From e0c5ad139adfe6469eb646801a1011b998e6043f Mon Sep 17 00:00:00 2001 From: SM Date: Mon, 21 Apr 2014 07:43:47 -1000 Subject: [PATCH 01/24] Update: WordPress 3.9 compatibility --- readme.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/readme.txt b/readme.txt index d575855..b99f6e5 100644 --- a/readme.txt +++ b/readme.txt @@ -3,8 +3,8 @@ Contributors: Archetyped Donate: https://gum.co/cnr-donate Tags: cornerstone, cms, content, management, system, structure, organization, sections Plugin Link: http://archetyped.com/tools/cornerstone/ -Requires at least: 3.8 -Tested up to: 3.8 +Requires at least: 3.9 +Tested up to: 3.9 Stable tag: trunk Enhanced content management for Wordpress From 7a9608962fd7342a04a0060040f576bbf4d47b2e Mon Sep 17 00:00:00 2001 From: SM Date: Wed, 15 Jul 2015 09:35:25 -1000 Subject: [PATCH 02/24] Update: WP version requirement (4.2.2) --- readme.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/readme.txt b/readme.txt index b99f6e5..05ebad3 100644 --- a/readme.txt +++ b/readme.txt @@ -3,8 +3,8 @@ Contributors: Archetyped Donate: https://gum.co/cnr-donate Tags: cornerstone, cms, content, management, system, structure, organization, sections Plugin Link: http://archetyped.com/tools/cornerstone/ -Requires at least: 3.9 -Tested up to: 3.9 +Requires at least: 4.2.2 +Tested up to: 4.2.2 Stable tag: trunk Enhanced content management for Wordpress From 001616d4c2a61661eda4699f90b495ebcc243096 Mon Sep 17 00:00:00 2001 From: SM Date: Wed, 15 Jul 2015 09:48:03 -1000 Subject: [PATCH 03/24] Remove: Legacy constructors --- includes/class.content-types.php | 23 ----------------------- includes/class.feeds.php | 7 ------- includes/class.posts.php | 4 ---- includes/class.structure.php | 7 ------- model.php | 4 ---- 5 files changed, 45 deletions(-) diff --git a/includes/class.content-types.php b/includes/class.content-types.php index a2ac672..1558fdb 100644 --- a/includes/class.content-types.php +++ b/includes/class.content-types.php @@ -153,13 +153,6 @@ class CNR_Content_Base extends CNR_Base { * @var array */ var $hooks = array(); - - /** - * Legacy Constructor - */ - function CNR_Content_Base($id = '', $parent = null) { - $this->__construct($id, $parent); - } /** * Constructor @@ -759,13 +752,6 @@ class CNR_Field_Type extends CNR_Content_Base { */ var $caller = null; - /** - * Legacy Constructor - */ - function CNR_Field_Type($id = '', $parent = null) { - $this->__construct($id, $parent); - } - /** * Constructor */ @@ -1440,15 +1426,6 @@ class CNR_Content_Type extends CNR_Content_Base { /* Constructors */ - /** - * Legacy constructor - * @param string $id Content type ID - */ - function CNR_Content_Type($id, $parent = false, $properties = null) { - $args = func_get_args(); - call_user_func_array(array(&$this, '__construct'), $args); - } - /** * Class constructor * @param string $id Content type ID diff --git a/includes/class.feeds.php b/includes/class.feeds.php index 13b7e46..6f071a1 100644 --- a/includes/class.feeds.php +++ b/includes/class.feeds.php @@ -12,13 +12,6 @@ */ class CNR_Feeds extends CNR_Base { - /** - * Legacy Constructor - */ - function CNR_Feeds() { - $this->__construct(); - } - /** * Constructor */ diff --git a/includes/class.posts.php b/includes/class.posts.php index 322fd69..d869107 100644 --- a/includes/class.posts.php +++ b/includes/class.posts.php @@ -80,10 +80,6 @@ class CNR_Post_Query extends CNR_Base { */ var $fetched; - function CNR_Post_Query( $args = null ) { - $this->__construct($args); - } - function __construct( $args = null ) { parent::__construct(); diff --git a/includes/class.structure.php b/includes/class.structure.php index 9063212..9b743b4 100644 --- a/includes/class.structure.php +++ b/includes/class.structure.php @@ -85,13 +85,6 @@ class CNR_Structure extends CNR_Base { /* Constructor */ - /** - * Legacy Constructor - */ - function CNR_Structure() { - $this->__construct(); - } - /** * Constructor */ diff --git a/model.php b/model.php index 2e77b72..219a0f2 100644 --- a/model.php +++ b/model.php @@ -98,10 +98,6 @@ class Cornerstone extends CNR_Base { var $feeds = null; /* Constructor */ - - function Cornerstone() { - $this->__construct(); - } function __construct() { //Parent Constructor From 279a6f5693c7be643bea90f19aa9ae2bce68f985 Mon Sep 17 00:00:00 2001 From: SM Date: Wed, 15 Jul 2015 09:52:55 -1000 Subject: [PATCH 04/24] Optimize: Correctly label static methods --- includes/class.content-types.php | 2 +- includes/class.posts.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/includes/class.content-types.php b/includes/class.content-types.php index 1558fdb..8bd64df 100644 --- a/includes/class.content-types.php +++ b/includes/class.content-types.php @@ -1204,7 +1204,7 @@ function uses_data() { * @param callback $handler Function to set as a handler * @param int $priority (optional) Priority of handler */ - function register_placeholder_handler($placeholder, $handler, $priority = 10) { + static function register_placeholder_handler($placeholder, $handler, $priority = 10) { if ( 'all' == $placeholder ) $placeholder = ''; else diff --git a/includes/class.posts.php b/includes/class.posts.php index d869107..795df66 100644 --- a/includes/class.posts.php +++ b/includes/class.posts.php @@ -493,7 +493,7 @@ function register_hooks() { * @param $depth Unused * @return array of Post Objects/Properties */ - function get_parents($post, $prop = '', $depth = '') { + static function get_parents($post, $prop = '', $depth = '') { $parents = get_post_ancestors($post = get_post($post, OBJECT, '')); if ( is_object($post) && !empty($parents) && ('id' != strtolower(trim($prop))) ) { //Retrieve post data for parents if full data or property other than post ID is required From 231d6f137f4236ef443e29a639124835cd1f51b2 Mon Sep 17 00:00:00 2001 From: SM Date: Wed, 15 Jul 2015 10:07:05 -1000 Subject: [PATCH 05/24] Add: Class autoloader (temporarily disabled) --- load.php | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 load.php diff --git a/load.php b/load.php new file mode 100644 index 0000000..889354c --- /dev/null +++ b/load.php @@ -0,0 +1,41 @@ + Date: Wed, 15 Jul 2015 10:10:26 -1000 Subject: [PATCH 06/24] Rename: `model.php` to `controller.php` --- model.php => controller.php | 486 ++++++++++++++++++------------------ 1 file changed, 243 insertions(+), 243 deletions(-) rename model.php => controller.php (95%) diff --git a/model.php b/controller.php similarity index 95% rename from model.php rename to controller.php index 219a0f2..6576655 100644 --- a/model.php +++ b/controller.php @@ -1,244 +1,244 @@ - array ( - 'file' => 'js/lib.core.js', - 'deps' => 'jquery' - ), - 'admin' => array ( - 'file' => 'js/lib.admin.js', - 'deps' => array('jquery', '[core]'), - 'context' => 'admin' - ), - 'inline_edit' => array ( - 'file' => 'js/lib.posts.inline_edit.js', - 'deps' => array('inline-edit-post','jquery', '[posts]'), - 'context' => 'admin_page_edit' - ) - ); - - /** - * Style files - * @var array - * @see CNR_Base::files - */ - var $styles = array ( - 'admin' => array ( - 'file' => 'css/admin.css', - 'context' => 'admin' - ) - ); - - /* Featured Content variables */ - - /** - * Category slug value that denotes a "featured" post - * @var string - * @see posts_featured_cat() - * @todo Remove need for this property - */ - var $posts_featured_cat = "feature"; - - /** - * Featured posts container - * @var CNR_Post_Query - */ - var $posts_featured = null; - - /* Children Content Variables */ - - /** - * Children posts - * @var CNR_Post_Query - */ - var $post_children_collection = null; - - /* Instance Variables */ - - /** - * Structure instance - * @var CNR_Structure - */ - var $structure = null; - - /** - * Media instance - * @var CNR_Media - */ - var $media = null; - - /** - * Post class instance - * @var CNR_Post - */ - var $post = null; - - /** - * Feeds instance - * @var CNR_Feeds - */ - var $feeds = null; - - /* Constructor */ - - function __construct() { - //Parent Constructor - parent::__construct(); - - //Init - $this->init(); - - //Special Queries - $this->posts_featured = new CNR_Post_Query( array( 'category' => $this->posts_featured_get_cat_id(), 'numberposts' => 4 ) ); - $this->post_children_collection = new CNR_Post_Query(); - + array ( + 'file' => 'js/lib.core.js', + 'deps' => 'jquery' + ), + 'admin' => array ( + 'file' => 'js/lib.admin.js', + 'deps' => array('jquery', '[core]'), + 'context' => 'admin' + ), + 'inline_edit' => array ( + 'file' => 'js/lib.posts.inline_edit.js', + 'deps' => array('inline-edit-post','jquery', '[posts]'), + 'context' => 'admin_page_edit' + ) + ); + + /** + * Style files + * @var array + * @see CNR_Base::files + */ + var $styles = array ( + 'admin' => array ( + 'file' => 'css/admin.css', + 'context' => 'admin' + ) + ); + + /* Featured Content variables */ + + /** + * Category slug value that denotes a "featured" post + * @var string + * @see posts_featured_cat() + * @todo Remove need for this property + */ + var $posts_featured_cat = "feature"; + + /** + * Featured posts container + * @var CNR_Post_Query + */ + var $posts_featured = null; + + /* Children Content Variables */ + + /** + * Children posts + * @var CNR_Post_Query + */ + var $post_children_collection = null; + + /* Instance Variables */ + + /** + * Structure instance + * @var CNR_Structure + */ + var $structure = null; + + /** + * Media instance + * @var CNR_Media + */ + var $media = null; + + /** + * Post class instance + * @var CNR_Post + */ + var $post = null; + + /** + * Feeds instance + * @var CNR_Feeds + */ + var $feeds = null; + + /* Constructor */ + + function __construct() { + //Parent Constructor + parent::__construct(); + + //Init + $this->init(); + + //Special Queries + $this->posts_featured = new CNR_Post_Query( array( 'category' => $this->posts_featured_get_cat_id(), 'numberposts' => 4 ) ); + $this->post_children_collection = new CNR_Post_Query(); + $this->post = new CNR_Post(); - $this->post->init(); - - - //Init class instances - $this->structure = new CNR_Structure(); - $this->structure->init(); - - $this->media = new CNR_Media(); - $this->media->init(); - - $this->feeds = new CNR_Feeds(); - $this->feeds->init(); - } - - /* Init */ - - /** - * Initialize environment - * Overrides parent method - * @see parent::init_env - * @return void - */ - function init_env() { - //Localization - $ldir = 'l10n'; - $lpath = $this->util->get_plugin_file_path($ldir, array(false, false)); - $lpath_abs = $this->util->get_file_path($ldir); - if ( is_dir($lpath_abs) ) { - load_plugin_textdomain($this->util->get_plugin_textdomain(), false, $lpath); - } - - //Context - add_action(( is_admin() ) ? 'admin_head' : 'wp_head', $this->m('set_client_context')); - } - - /* Methods */ - - /*-** Request **-*/ - - /** - * Output current context to client-side - * @uses `wp_head` action hook - * @uses `admin_head` action hook - * @return void - */ - function set_client_context() { - $ctx = new stdClass(); - $ctx->context = $this->util->get_context(); - $this->util->extend_client_object($ctx, true); - } - - /*-** Child Content **-*/ - - /** - * Gets children posts of specified page and stores them for later use - * This method hooks into 'the_posts' filter to retrieve child posts for any single page retrieved by WP - * @return array $posts Posts array (required by 'the_posts' filter) - * @param array $posts Array of Posts (@see WP_QUERY) - */ - function post_children_get($posts) { - //Global variables - global $wp_query; - - //Reset post children collection - $this->post_children_collection->init(); - - //Stop here if post is not a page - if ( ! is_page() || empty($posts) ) - return $posts; - - //Get children posts - $post =& $posts[0]; - $this->post_children_collection =& CNR_Post::get_children($post); - - //Return posts (required by filter) - return $posts; - } - - /*-** Featured Content **-*/ - - /** - * Retrieves featured post category object - * @return object Featured post category object - * @todo integrate into CNR_Post_Query - */ - function posts_featured_get_cat() { - static $cat = null; - - //Only fetch category object if it hasn't already been retrieved - if (is_null($cat) || !is_object($cat)) { - //Retrieve category object - if (is_int($this->posts_featured_cat)) { - $cat = get_category((int)$this->posts_featured_cat); - } - elseif (is_string($this->posts_featured_cat) && strlen($this->posts_featured_cat) > 0) { - $cat = get_category_by_slug($this->posts_featured_cat); - } - } - - return $cat; - } - - /** - * @todo integrate into CNR_Post_Query - */ - function posts_featured_get_cat_id() { - static $id = ''; - if ($id == '') { - $cat = $this->posts_featured_get_cat(); - if (!is_null($cat) && is_object($cat) && $this->util->property_exists($cat, 'cat_ID')) - $id = $cat->cat_ID; - } - return $id; - } - - /** - * Checks if post has content to display - * @param object $post (optional) Post object - * @return bool TRUE if post has content, FALSE otherwise - * @todo Review for deletion/relocation - */ - function post_has_content($post = null) { - if ( !$this->util->check_post($post) ) - return false; - if ( isset($post->post_content) && trim($post->post_content) != '' ) - return true; - return false; - } -} - -?> + $this->post->init(); + + + //Init class instances + $this->structure = new CNR_Structure(); + $this->structure->init(); + + $this->media = new CNR_Media(); + $this->media->init(); + + $this->feeds = new CNR_Feeds(); + $this->feeds->init(); + } + + /* Init */ + + /** + * Initialize environment + * Overrides parent method + * @see parent::init_env + * @return void + */ + function init_env() { + //Localization + $ldir = 'l10n'; + $lpath = $this->util->get_plugin_file_path($ldir, array(false, false)); + $lpath_abs = $this->util->get_file_path($ldir); + if ( is_dir($lpath_abs) ) { + load_plugin_textdomain($this->util->get_plugin_textdomain(), false, $lpath); + } + + //Context + add_action(( is_admin() ) ? 'admin_head' : 'wp_head', $this->m('set_client_context')); + } + + /* Methods */ + + /*-** Request **-*/ + + /** + * Output current context to client-side + * @uses `wp_head` action hook + * @uses `admin_head` action hook + * @return void + */ + function set_client_context() { + $ctx = new stdClass(); + $ctx->context = $this->util->get_context(); + $this->util->extend_client_object($ctx, true); + } + + /*-** Child Content **-*/ + + /** + * Gets children posts of specified page and stores them for later use + * This method hooks into 'the_posts' filter to retrieve child posts for any single page retrieved by WP + * @return array $posts Posts array (required by 'the_posts' filter) + * @param array $posts Array of Posts (@see WP_QUERY) + */ + function post_children_get($posts) { + //Global variables + global $wp_query; + + //Reset post children collection + $this->post_children_collection->init(); + + //Stop here if post is not a page + if ( ! is_page() || empty($posts) ) + return $posts; + + //Get children posts + $post =& $posts[0]; + $this->post_children_collection =& CNR_Post::get_children($post); + + //Return posts (required by filter) + return $posts; + } + + /*-** Featured Content **-*/ + + /** + * Retrieves featured post category object + * @return object Featured post category object + * @todo integrate into CNR_Post_Query + */ + function posts_featured_get_cat() { + static $cat = null; + + //Only fetch category object if it hasn't already been retrieved + if (is_null($cat) || !is_object($cat)) { + //Retrieve category object + if (is_int($this->posts_featured_cat)) { + $cat = get_category((int)$this->posts_featured_cat); + } + elseif (is_string($this->posts_featured_cat) && strlen($this->posts_featured_cat) > 0) { + $cat = get_category_by_slug($this->posts_featured_cat); + } + } + + return $cat; + } + + /** + * @todo integrate into CNR_Post_Query + */ + function posts_featured_get_cat_id() { + static $id = ''; + if ($id == '') { + $cat = $this->posts_featured_get_cat(); + if (!is_null($cat) && is_object($cat) && $this->util->property_exists($cat, 'cat_ID')) + $id = $cat->cat_ID; + } + return $id; + } + + /** + * Checks if post has content to display + * @param object $post (optional) Post object + * @return bool TRUE if post has content, FALSE otherwise + * @todo Review for deletion/relocation + */ + function post_has_content($post = null) { + if ( !$this->util->check_post($post) ) + return false; + if ( isset($post->post_content) && trim($post->post_content) != '' ) + return true; + return false; + } +} + +?> From a017907d7660338f637ebaf2e85d4cc1f26128e5 Mon Sep 17 00:00:00 2001 From: SM Date: Wed, 15 Jul 2015 10:14:02 -1000 Subject: [PATCH 07/24] Remove: Manual file inclusion (`require_once` etc.) --- controller.php | 7 ------- includes/class.base.php | 2 -- includes/class.content-types.php | 2 -- includes/class.feeds.php | 3 --- includes/class.media.php | 2 -- includes/class.posts.php | 2 -- includes/class.structure.php | 3 --- 7 files changed, 21 deletions(-) diff --git a/controller.php b/controller.php index 6576655..d90da5c 100644 --- a/controller.php +++ b/controller.php @@ -1,12 +1,5 @@ Date: Wed, 15 Jul 2015 10:15:36 -1000 Subject: [PATCH 08/24] Add: functions.php --- functions.php | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 functions.php diff --git a/functions.php b/functions.php new file mode 100644 index 0000000..0603f66 --- /dev/null +++ b/functions.php @@ -0,0 +1,7 @@ + Date: Wed, 15 Jul 2015 10:16:52 -1000 Subject: [PATCH 09/24] Refactor: Move core functions to `functions.php` (from `main.php`) --- functions.php | 191 +++++++++++++++++++++++++++++++++++++++++++++++++ main.php | 193 +------------------------------------------------- 2 files changed, 192 insertions(+), 192 deletions(-) diff --git a/functions.php b/functions.php index 0603f66..a3378fe 100644 --- a/functions.php +++ b/functions.php @@ -5,3 +5,194 @@ * @package Cornerstone * @author Archetyped */ + +/* Template tags */ + +/** + * Outputs feed links based on current page + * @return void + */ +function cnr_the_feed_links() { + global $cnr; + $cnr->feeds->the_links(); +} + +/*-** Child Content **-*/ + +function cnr_is_section() { + return ( is_page() && cnr_have_children() ) ? true : false; +} + +/** + * Checks if current post/page has children elements + * @return bool TRUE if post/page has children, FALSE otherwise + */ +function cnr_have_children() { + global $cnr; + return $cnr->post_children_collection->has(); +} + +/** + * Prepares next child post for output to page + * + * @return void + */ +function cnr_next_child() { + global $cnr; + $cnr->post_children_collection->next(); +} + +/** + * Returns number of children in current request + * May not return total number of existing children (e.g. if output is paged, etc.) + * @return int Number of children returned in current request + */ +function cnr_children_count() { + global $cnr; + return $cnr->post_children_collection->count(); +} + +/** + * Returns total number of existing children + * @return int Total number of children + */ +function cnr_children_found() { + global $cnr; + return $cnr->post_children_collection->found(); +} + +/** + * Returns total number of pages of children + * Based on 'posts_per_page' option + * @return int Maximum number of pages + */ +function cnr_children_max_num_pages() { + global $cnr; + return $cnr->post_children_collection->max_num_pages(); +} + +/** + * Checks if current child item is the first child item + * @return bool TRUE if current item is first, FALSE otherwise + */ +function cnr_is_first_child() { + global $cnr; + return $cnr->post_children_collection->is_first(); +} + +/** + * Checks if current child item is the last child item + * @return bool TRUE if current item is last, FALSE otherwise + */ +function cnr_is_last_child() { + global $cnr; + return $cnr->post_children_collection->is_last(); +} + +/*-** Featured Content **-*/ + +/** + * Retrieves featured posts + * @return array Featured posts matching criteria + * @param int $limit (optional) Maximum number of featured posts to retrieve + * @param int|bool $parent (optional) Section to get featured posts of (Defaults to current section). FALSE if latest featured posts should be retrieved regardless of section + */ +function cnr_get_featured($limit = 0, $parent = null) { + global $cnr; + return $cnr->posts_featured->get($limit, $parent); +} + +function cnr_in_featured($post_id = null) { + global $cnr; + return $cnr->posts_featured->contains($post_id); +} + +function cnr_have_featured() { + global $cnr; + return $cnr->posts_featured->has(); +} + +function cnr_next_featured() { + global $cnr; + return $cnr->posts_featured->next(); + +} + +function cnr_current_featured() { + global $cnr; + return $cnr->posts_featured->current(); +} + +function cnr_is_first_featured() { + global $cnr; + return $cnr->posts_featured->is_first(); +} + +function cnr_is_last_featured() { + global $cnr; + return $cnr->posts_featured->is_last(); +} + +function cnr_featured_count() { + global $cnr; + return $cnr->posts_featured->count(); +} + +/** + * Returns total number of found posts + * @return int Total number of posts + */ +function cnr_featured_found() { + global $cnr; + return $cnr->posts_featured->found(); +} + +/*-** Post-Specific **-*/ + +/** + * Checks if post has content to display + * @param object $post (optional) Post object + * @return bool TRUE if post has content, FALSE otherwise + */ +function cnr_has_content($post = null) { + global $cnr; + return $cnr->post_has_content($post); +} + + /* Images */ + +function cnr_get_attachments($post = null) { + $m = new CNR_Media(); + return $m->post_get_attachments($post); +} + +function cnr_get_filesize($post = null, $formatted = true) { + $m = new CNR_Media(); + return $m->get_attachment_filesize($post, $formatted); +} + + /* Section */ + +/** + * Retrieves the post's section data + * @uses CNR_Post::get_section() + * @param string $data (optional) Type of data to return (Default: ID) + * Possible values: + * NULL Full section post object + * Column name Post column data (if exists) + * + * @param int $id (optional) Post ID (Default: current post) + * @return mixed post's section (or column data if specified via $data parameter) + */ +function cnr_get_the_section($data = 'ID', $id = null) { + return CNR_Post::get_section($id, $data); +} + +/** + * Prints the post's section data + * @uses CNR_Post::the_section() + * @param string $data (optional) Type of data to return (Default: ID) + */ +function cnr_the_section($data = 'ID') { + CNR_Post::the_section(null, $data); +} \ No newline at end of file diff --git a/main.php b/main.php index c1715d8..900eb3d 100644 --- a/main.php +++ b/main.php @@ -12,195 +12,4 @@ * @package Cornerstone */ require_once('model.php'); -$cnr = new Cornerstone(); - -/* Template tags */ - -/** - * Outputs feed links based on current page - * @return void - */ -function cnr_the_feed_links() { - global $cnr; - $cnr->feeds->the_links(); -} - -/*-** Child Content **-*/ - -function cnr_is_section() { - return ( is_page() && cnr_have_children() ) ? true : false; -} - -/** - * Checks if current post/page has children elements - * @return bool TRUE if post/page has children, FALSE otherwise - */ -function cnr_have_children() { - global $cnr; - return $cnr->post_children_collection->has(); -} - -/** - * Prepares next child post for output to page - * - * @return void - */ -function cnr_next_child() { - global $cnr; - $cnr->post_children_collection->next(); -} - -/** - * Returns number of children in current request - * May not return total number of existing children (e.g. if output is paged, etc.) - * @return int Number of children returned in current request - */ -function cnr_children_count() { - global $cnr; - return $cnr->post_children_collection->count(); -} - -/** - * Returns total number of existing children - * @return int Total number of children - */ -function cnr_children_found() { - global $cnr; - return $cnr->post_children_collection->found(); -} - -/** - * Returns total number of pages of children - * Based on 'posts_per_page' option - * @return int Maximum number of pages - */ -function cnr_children_max_num_pages() { - global $cnr; - return $cnr->post_children_collection->max_num_pages(); -} - -/** - * Checks if current child item is the first child item - * @return bool TRUE if current item is first, FALSE otherwise - */ -function cnr_is_first_child() { - global $cnr; - return $cnr->post_children_collection->is_first(); -} - -/** - * Checks if current child item is the last child item - * @return bool TRUE if current item is last, FALSE otherwise - */ -function cnr_is_last_child() { - global $cnr; - return $cnr->post_children_collection->is_last(); -} - -/*-** Featured Content **-*/ - -/** - * Retrieves featured posts - * @return array Featured posts matching criteria - * @param int $limit (optional) Maximum number of featured posts to retrieve - * @param int|bool $parent (optional) Section to get featured posts of (Defaults to current section). FALSE if latest featured posts should be retrieved regardless of section - */ -function cnr_get_featured($limit = 0, $parent = null) { - global $cnr; - return $cnr->posts_featured->get($limit, $parent); -} - -function cnr_in_featured($post_id = null) { - global $cnr; - return $cnr->posts_featured->contains($post_id); -} - -function cnr_have_featured() { - global $cnr; - return $cnr->posts_featured->has(); -} - -function cnr_next_featured() { - global $cnr; - return $cnr->posts_featured->next(); - -} - -function cnr_current_featured() { - global $cnr; - return $cnr->posts_featured->current(); -} - -function cnr_is_first_featured() { - global $cnr; - return $cnr->posts_featured->is_first(); -} - -function cnr_is_last_featured() { - global $cnr; - return $cnr->posts_featured->is_last(); -} - -function cnr_featured_count() { - global $cnr; - return $cnr->posts_featured->count(); -} - -/** - * Returns total number of found posts - * @return int Total number of posts - */ -function cnr_featured_found() { - global $cnr; - return $cnr->posts_featured->found(); -} - -/*-** Post-Specific **-*/ - -/** - * Checks if post has content to display - * @param object $post (optional) Post object - * @return bool TRUE if post has content, FALSE otherwise - */ -function cnr_has_content($post = null) { - global $cnr; - return $cnr->post_has_content($post); -} - - /* Images */ - -function cnr_get_attachments($post = null) { - $m = new CNR_Media(); - return $m->post_get_attachments($post); -} - -function cnr_get_filesize($post = null, $formatted = true) { - $m = new CNR_Media(); - return $m->get_attachment_filesize($post, $formatted); -} - - /* Section */ - -/** - * Retrieves the post's section data - * @uses CNR_Post::get_section() - * @param string $data (optional) Type of data to return (Default: ID) - * Possible values: - * NULL Full section post object - * Column name Post column data (if exists) - * - * @param int $id (optional) Post ID (Default: current post) - * @return mixed post's section (or column data if specified via $data parameter) - */ -function cnr_get_the_section($data = 'ID', $id = null) { - return CNR_Post::get_section($id, $data); -} - -/** - * Prints the post's section data - * @uses CNR_Post::the_section() - * @param string $data (optional) Type of data to return (Default: ID) - */ -function cnr_the_section($data = 'ID') { - CNR_Post::the_section(null, $data); -} +$cnr = new Cornerstone(); \ No newline at end of file From 32796f59255ca1a6a12127f047ffc4eda73d583f Mon Sep 17 00:00:00 2001 From: SM Date: Wed, 15 Jul 2015 10:21:41 -1000 Subject: [PATCH 10/24] Refactor: Initialization * Use action to load CNR * Move CNR instance init from `main.php` to `load.php` --- load.php | 4 +--- main.php | 16 ++++++++++++---- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/load.php b/load.php index 889354c..28fe0bc 100644 --- a/load.php +++ b/load.php @@ -33,9 +33,7 @@ function cnr_autoload($classname) { //spl_autoload_register('cnr_autoload'); /* Load Assets */ -/* $path = dirname(__FILE__) . '/'; require_once $path . 'controller.php'; $GLOBALS['cnr'] = new Cornerstone(); -require_once $path . 'functions.php'; -*/ \ No newline at end of file +require_once $path . 'functions.php'; \ No newline at end of file diff --git a/main.php b/main.php index 900eb3d..49c08b8 100644 --- a/main.php +++ b/main.php @@ -6,10 +6,18 @@ Version: dev (BETA) Author: Archetyped Author URI: http://archetyped.com +Support URI: https://github.com/archetyped/cornerstone/wiki/Reporting-Issues */ - +/* +Copyright 2015 Archetyped (support@archetyped.com) +*/ +$cnr = null; /** - * @package Cornerstone + * Initialize CNR */ -require_once('model.php'); -$cnr = new Cornerstone(); \ No newline at end of file +function cnr_init() { + $path = dirname(__FILE__) . '/'; + require_once $path . 'load.php'; +} + +add_action('init', 'cnr_init', 1); \ No newline at end of file From 54d247d88299b28d1038e3de02532694f523da13 Mon Sep 17 00:00:00 2001 From: SM Date: Thu, 16 Jul 2015 08:57:58 -1000 Subject: [PATCH 11/24] Refactor: Move Content Type functions to `functions.php` --- functions.php | 58 ++++++++++++++++++++++++++++++++ includes/class.content-types.php | 58 -------------------------------- 2 files changed, 58 insertions(+), 58 deletions(-) diff --git a/functions.php b/functions.php index a3378fe..be22199 100644 --- a/functions.php +++ b/functions.php @@ -195,4 +195,62 @@ function cnr_get_the_section($data = 'ID', $id = null) { */ function cnr_the_section($data = 'ID') { CNR_Post::the_section(null, $data); +} + +/* Content Types */ + +/** + * Register handler for a placeholder in a content type template + * Placeholders allow templates to be populated with dynamic content at runtime + * Multiple handlers can be registered for a placeholder, + * thus allowing custom handlers to override default processing, etc. + * @uses CNR_Field_Type::register_placeholder_handler() to register placeholder + * @param string $placeholder Placeholder identifier + * @param callback $handler Callback function to use as handler for placeholder + * @param int $priority (optional) Priority of registered handler (Default: 10) + */ +function cnr_register_placeholder_handler($placeholder, $handler, $priority = 10) { + CNR_Field_Type::register_placeholder_handler($placeholder, $handler, $priority); +} + +/** + * Checks if data exists for specified field + * @global $cnr_content_utilities + * @param string $field_id ID of field to check for data + * @param int|obj $item (optional) Post ID or object to check for field data (Default: global post) + * @return bool TRUE if field data exists + */ +function cnr_has_data($field_id = null, $item = null) { + global $cnr_content_utilities; + return $cnr_content_utilities->has_item_data($item, $field_id); +} + +/** + * Retrieve data from a field + * @global $cnr_content_utilities + * @see CNR_Content_Utilities::get_item_data() for more information + * @param string $field_id ID of field to retrieve + * @param string $layout (optional) Name of layout to use when returning data + * @param array $attr (optional) Additional attributes to pass to field + * @param int|object $item (optional) Post object to retrieve data from (Default: global post object) + * @param mixed $default Default value to return in case of errors (invalid field, no data, etc.) + * @return mixed Specified field data + */ +function cnr_get_data($field_id = null, $layout = 'display', $attr = null, $item = null, $default = '') { + global $cnr_content_utilities; + return $cnr_content_utilities->get_item_data($item, $field_id, $layout, $default, $attr); +} + +/** + * Prints an item's field data + * @see CNR_Content_Utilities::the_item_data() for more information + * @param string $field_id Name of field to retrieve + * @param string $layout(optional) Layout to use when returning field data (Default: display) + * @param array $attr Additional items to pass to field + * @param int|object $item(optional) Content item to retrieve field from (Default: null - global $post object will be used) + * @param mixed $default Default value to return in case of errors, etc. + */ +function cnr_the_data($field_id = null, $layout = 'display', $attr = null, $item = null, $default = '') { + global $cnr_content_utilities; + $cnr_content_utilities->the_item_data($item, $field_id, $layout, $default, $attr); } \ No newline at end of file diff --git a/includes/class.content-types.php b/includes/class.content-types.php index e20680c..b5c3296 100644 --- a/includes/class.content-types.php +++ b/includes/class.content-types.php @@ -12,64 +12,6 @@ $cnr_content_utilities = new CNR_Content_Utilities(); $cnr_content_utilities->init(); -/* Functions */ - -/** - * Register handler for a placeholder in a content type template - * Placeholders allow templates to be populated with dynamic content at runtime - * Multiple handlers can be registered for a placeholder, - * thus allowing custom handlers to override default processing, etc. - * @uses CNR_Field_Type::register_placeholder_handler() to register placeholder - * @param string $placeholder Placeholder identifier - * @param callback $handler Callback function to use as handler for placeholder - * @param int $priority (optional) Priority of registered handler (Default: 10) - */ -function cnr_register_placeholder_handler($placeholder, $handler, $priority = 10) { - CNR_Field_Type::register_placeholder_handler($placeholder, $handler, $priority); -} - -/** - * Checks if data exists for specified field - * @global $cnr_content_utilities - * @param string $field_id ID of field to check for data - * @param int|obj $item (optional) Post ID or object to check for field data (Default: global post) - * @return bool TRUE if field data exists - */ -function cnr_has_data($field_id = null, $item = null) { - global $cnr_content_utilities; - return $cnr_content_utilities->has_item_data($item, $field_id); -} - -/** - * Retrieve data from a field - * @global $cnr_content_utilities - * @see CNR_Content_Utilities::get_item_data() for more information - * @param string $field_id ID of field to retrieve - * @param string $layout (optional) Name of layout to use when returning data - * @param array $attr (optional) Additional attributes to pass to field - * @param int|object $item (optional) Post object to retrieve data from (Default: global post object) - * @param mixed $default Default value to return in case of errors (invalid field, no data, etc.) - * @return mixed Specified field data - */ -function cnr_get_data($field_id = null, $layout = 'display', $attr = null, $item = null, $default = '') { - global $cnr_content_utilities; - return $cnr_content_utilities->get_item_data($item, $field_id, $layout, $default, $attr); -} - -/** - * Prints an item's field data - * @see CNR_Content_Utilities::the_item_data() for more information - * @param string $field_id Name of field to retrieve - * @param string $layout(optional) Layout to use when returning field data (Default: display) - * @param array $attr Additional items to pass to field - * @param int|object $item(optional) Content item to retrieve field from (Default: null - global $post object will be used) - * @param mixed $default Default value to return in case of errors, etc. - */ -function cnr_the_data($field_id = null, $layout = 'display', $attr = null, $item = null, $default = '') { - global $cnr_content_utilities; - $cnr_content_utilities->the_item_data($item, $field_id, $layout, $default, $attr); -} - /* Hooks */ //Default placeholder handlers From 9d773e9a06e21870a36befa33491785d1804b94b Mon Sep 17 00:00:00 2001 From: SM Date: Thu, 16 Jul 2015 09:07:24 -1000 Subject: [PATCH 12/24] Refactor: Split classes into separate Files (Content Types) --- includes/class.content-types.php | 2958 -------------------------- includes/class.content_base.php | 613 ++++++ includes/class.content_type.php | 397 ++++ includes/class.content_utilities.php | 1247 +++++++++++ includes/class.field.php | 4 + includes/class.field_type.php | 699 ++++++ 6 files changed, 2960 insertions(+), 2958 deletions(-) create mode 100644 includes/class.content_base.php create mode 100644 includes/class.content_type.php create mode 100644 includes/class.content_utilities.php create mode 100644 includes/class.field.php create mode 100644 includes/class.field_type.php diff --git a/includes/class.content-types.php b/includes/class.content-types.php index b5c3296..0f4f278 100644 --- a/includes/class.content-types.php +++ b/includes/class.content-types.php @@ -23,2963 +23,5 @@ cnr_register_placeholder_handler('data_ext', array('CNR_Field_Type', 'process_placeholder_data_ext')); cnr_register_placeholder_handler('rich_editor', array('CNR_Field_Type', 'process_placeholder_rich_editor')); -/** - * Content Types - Base Class - * Core properties/methods for Content Type derivative classes - * @package Cornerstone - * @subpackage Content Types - * @author Archetyped - */ -class CNR_Content_Base extends CNR_Base { - /** - * Base class name - * @var string - */ - var $base_class = 'cnr_content_base'; - - /** - * @var string Unique name - */ - var $id = ''; - - /** - * Reference to parent object that current instance inherits from - * @var object - */ - var $parent = null; - - /** - * Title - * @var string - */ - var $title = ''; - - /** - * Plural Title - * @var string - */ - var $title_plural = ''; - - /** - * @var string Short description - */ - var $description = ''; - - /** - * @var array Object Properties - */ - var $properties = array(); - - /** - * Data for object - * May also contain data for nested objects - * @var mixed - */ - var $data = null; - - /** - * @var array Script resources to include for object - */ - var $scripts = array(); - - /** - * @var array CSS style resources to include for object - */ - var $styles = array(); - - /** - * Hooks (Filters/Actions) for object - * @var array - */ - var $hooks = array(); - - /** - * Constructor - */ - function __construct($id = '', $parent = null) { - parent::__construct(); - $id = trim($id); - $this->id = $id; - if ( is_bool($parent) && $parent ) - $parent = $id; - $this->set_parent($parent); - } - - /* Getters/Setters */ - - /** - * Checks if the specified path exists in the object - * @param array $path Path to check for - * @return bool TRUE if path exists in object, FALSE otherwise - */ - function path_isset($path = '') { - //Stop execution if no path is supplied - if ( empty($path) ) - return false; - $args = func_get_args(); - $path = $this->util->build_path($args); - $item =& $this; - //Iterate over path and check if each level exists before moving on to the next - for ($x = 0; $x < count($path); $x++) { - if ( $this->util->property_exists($item, $path[$x]) ) { - //Set $item as reference to next level in path for next iteration - $item =& $this->util->get_property($item, $path[$x]); - //$item =& $item[ $path[$x] ]; - } else { - return false; - } - } - return true; - } - - /** - * Retrieves a value from object using a specified path - * Checks to make sure path exists in object before retrieving value - * @param array $path Path to retrieve value from. Each item in array is a deeper dimension - * @return mixed Value at specified path - */ - function &get_path_value($path = '') { - $ret = ''; - $path = $this->util->build_path(func_get_args()); - if ( $this->path_isset($path) ) { - $ret =& $this; - for ($x = 0; $x < count($path); $x++) { - if ( 0 == $x ) - $ret =& $ret->{ $path[$x] }; - else - $ret =& $ret[ $path[$x] ]; - } - } - return $ret; - } - - /** - * Search for specified member value in field type ancestors - * @param string $member Name of object member to search (e.g. properties, layout, etc.) - * @param string $name Value to retrieve from member - * @return mixed Member value if found (Default: empty string) - */ - function get_parent_value($member, $name = '', $default = '') { - $parent =& $this->get_parent(); - return $this->get_object_value($parent, $member, $name, $default, 'parent'); - } - - /** - * Retrieves specified member value - * Handles inherited values - * Merging corresponding parents if value is an array (e.g. for property groups) - * @param string|array $member Member to search. May also contain a path to the desired member - * @param string $name Value to retrieve from member - * @param mixed $default Default value if no value found (Default: empty string) - * @param string $dir Direction to move through hierarchy to find value - * Possible Values: - * parent (default) - Search through field parents - * current - Do not search through connected objects - * container - Search through field containers - * caller - Search through field callers - * @return mixed Specified member value - */ - function get_member_value($member, $name = '', $default = '', $dir = 'parent') { - //Check if path to member is supplied - $path = array(); - if ( is_array($member) && isset($member['tag']) ) { - if ( isset($member['attributes']['ref_base']) ) { - if ( 'root' != $member['attributes']['ref_base'] ) - $path[] = $member['attributes']['ref_base']; - } else { - $path[] = 'properties'; - } - - $path[] = $member['tag']; - } else { - $path = $member; - } - - $path = $this->util->build_path($path, $name); - //Set defaults and prepare data - $val = $default; - $inherit = false; - $inherit_tag = '{inherit}'; - - /* Determine whether the value must be retrieved from a parent/container object - * Conditions: - * > Path does not exist in current field - * > Path exists and is not an object, but at least one of the following is true: - * > Value at path is an array (e.g. properties, elements, etc. array) - * > Parent/container values should be merged with retrieved array - * > Value at path is a string that inherits from another field - * > Value from other field will be retrieved and will replace inheritance placeholder in retrieved value - */ - - $deeper = false; - - if ( !$this->path_isset($path) ) - $deeper = true; - else { - $val = $this->get_path_value($path); - if ( !is_object($val) && ( is_array($val) || ($inherit = strpos($val, $inherit_tag)) !== false ) ) - $deeper = true; - else - $deeper = false; - } - if ( $deeper && 'current' != $dir ) { - //Get Parent value (recursive) - $ex_val = ( 'parent' != $dir ) ? $this->get_container_value($member, $name, $default) : $this->get_parent_value($member, $name, $default); - //Handle inheritance - if ( is_array($val) ) { - //Combine Arrays - if ( is_array($ex_val) ) - $val = array_merge($ex_val, $val); - } elseif ( $inherit !== false ) { - //Replace placeholder with inherited string - $val = str_replace($inherit_tag, $ex_val, $val); - } else { - //Default: Set parent value as value - $val = $ex_val; - } - } - - return $val; - } - - /** - * Search for specified member value in an object - * @param object $object Reference to object to retrieve value from - * @param string $member Name of object member to search (e.g. properties, layout, etc.) - * @param string $name (optional) Value to retrieve from member - * @param mixed $default (optional) Default value to use if no value found (Default: empty string) - * @param string $dir Direction to move through hierarchy to find value @see CNR_Field_Type::get_member_value() for possible values - * @return mixed Member value if found (Default: $default) - */ - function get_object_value(&$object, $member, $name = '', $default = '', $dir = 'parent') { - $ret = $default; - if ( is_object($object) && method_exists($object, 'get_member_value') ) - $ret = $object->get_member_value($member, $name, $default, $dir); - return $ret; - } - - /** - * Retrieve value from data member - * @param bool $top (optional) Whether to traverse through the field hierarchy to get data for field (Default: TRUE) - * @return mixed Value at specified path - */ - function get_data($top = true) { - $top = !!$top; - $obj = $this; - $obj_path = array($this); - $path = array(); - //Iterate through hiearchy to get top-most object - while ( !empty($obj) ) { - $new = null; - //Try to get caller first - if ( method_exists($obj, 'get_caller') ) { - $checked = true; - $new = $obj->get_caller(); - } - //Try to get container if no caller found - if ( empty($new) && method_exists($obj, 'get_container') ) { - $checked = true; - $new = $obj->get_container(); - } - - $obj = $new; - - //Stop iteration - if ( !empty($obj) ) { - //Add object to path if it is valid - $obj_path[] = $obj; - } - } - - //Check each object (starting with top-most) for matching data for current field - - //Reverse array - $obj_path = array_reverse($obj_path); - //Build path for data location - foreach ( $obj_path as $obj ) { - if ( $this->util->property_exists($obj, 'id') ) - $path[] = $obj->id; - } - - //Iterate through objects - while ( !empty($obj_path) ) { - //Get next object - $obj = array_shift($obj_path); - //Shorten path - array_shift($path); - //Check for value in object and stop iteration if matching data found - if ( ($val = $this->get_object_value($obj, 'data', $path, null, 'current')) && !is_null($val) ) { - break; - } - } - - return $val; - } - - /** - * Sets value in data member - * Sets value to data member itself by default - * @param mixed $value Value to set - * @param string|array $name Name of value to set (Can also be path to value) - */ - function set_data($value, $name = '') { - $ref =& $this->get_path_value('data', $name); - $ref = $value; - } - - /** - * Retrieve base_class property - * @return string base_class property of current class/instance object - */ - function get_base_class() { - $ret = ''; - if ( isset($this) ) - $ret = $this->base_class; - else { - $ret = CNR_Utilities::get_property(__CLASS__, 'base_class'); - } - - return $ret; - } - - /** - * Sets parent object of current instance - * Parent objects must be the same object type as current instance - * @param string|object $parent Parent ID or reference - */ - function set_parent($parent) { - if ( !empty($parent) ) { - //Validate parent object - if ( is_array($parent) ) - $parent =& $parent[0]; - - //Retrieve reference object if ID was supplied - if ( is_string($parent) ) { - $parent = trim($parent); - //Check for existence of parent - $lookup = $this->base_class . 's'; - if ( isset($GLOBALS[$lookup][$parent]) ) { - //Get reference to parent - $parent =& $GLOBALS[$lookup][$parent]; - } - } - - //Set reference to parent field type - if ( is_a($parent, $this->base_class) ) { - $this->parent =& $parent; - } - } - } - - /** - * Retrieve field type parent - * @return CNR_Field_Type Reference to parent field - */ - function &get_parent() { - return $this->parent; - } - - /** - * Retrieves field ID - * @param string|CNR_Field|array $field (optional) Field object or ID of field or options array - * @return string|bool Field ID, FALSE if $field is invalid - */ - function get_id($field = null) { - $ret = false; - if ( ( !is_object($field) || !is_a($field, 'cnr_field_type') ) && isset($this) ) { - $field =& $this; - } - - if ( is_a($field, CNR_Field_Type::get_base_class()) ) - $id = $field->id; - - if ( is_string($id) ) - $ret = trim($id); - - //Setup options - $options_def = array('format' => null); - //Get options array - $num_args = func_num_args(); - $options = ( $num_args > 0 && ( $last_arg = func_get_arg($num_args - 1) ) && is_array($last_arg) ) ? $last_arg : array(); - $options = wp_parse_args($options, $options_def); - //Check if field should be formatted - if ( is_string($ret) && !empty($options['format']) ) { - //Clear format option if it is an invalid value - if ( is_bool($options['format']) || is_int($options['format']) ) - $options['format'] = null; - //Setup values - $wrap = array('open' => '[', 'close' => ']'); - if ( isset($options['wrap']) && is_array($options['wrap']) ) - $wrap = wp_parse_args($options['wrap'], $wrap); - $wrap_trailing = ( isset($options['wrap_trailing']) ) ? !!$options['wrap_trailing'] : true; - switch ( $options['format'] ) { - case 'attr_id' : - $wrap = (array('open' => '_', 'close' => '_')); - $wrap_trailing = false; - break; - } - $c = $field->get_caller(); - $field_id = array($ret); - while ( !!$c ) { - //Add ID of current field to array - if ( isset($c->id) && is_a($c, $this->base_class) ) - $field_id[] = $c->id; - $c = ( method_exists($c, 'get_caller') ) ? $c->get_caller() : null; - } - - //Add prefix to ID value - $field_id[] = 'attributes'; - - //Convert array to string - return $field->prefix . $wrap['open'] . implode($wrap['close'] . $wrap['open'], array_reverse($field_id)) . ( $wrap_trailing ? $wrap['close'] : ''); - } - return $ret; - } - - /** - * Set object title - * @param string $title Title for object - * @param string $plural Plural form of title - */ - function set_title($title = '', $plural = '') { - $this->title = strip_tags(trim($title)); - if ( isset($plural) ) - $this->title_plural = strip_tags(trim($plural)); - } - - /** - * Retrieve object title - * @param bool $plural TRUE if plural title should be retrieved, FALSE otherwise (Default: FALSE) - */ - function get_title($plural = false) { - $dir = 'current'; - //Singular - if ( !$plural ) - return $this->get_member_value('title', '','', $dir); - //Plural - $title = $this->get_member_value('title_plural', '', '', $dir); - if ( empty($title) ) { - //Use singular title for plural base - $title = $this->get_member_value('title', '', '', $dir); - //Determine technique for making title plural - //Get last letter - if ( !empty($title) ) { - $tail = substr($title, -1); - switch ( $tail ) { - case 's' : - $title .= 'es'; - break; - case 'y' : - $title = substr($title, 0, -1) . 'ies'; - break; - default : - $title .= 's'; - } - } - } - return $title; - } - - /** - * Set object description - * @param string $description Description for object - */ - function set_description($description = '') { - $this->description = strip_tags(trim($description)); - } - - /** - * Retrieve object description - * @return string Object description - */ - function get_description() { - $dir = 'current'; - return $this->get_member_value('description', '','', $dir); - return $desc; - } - - /*-** Hooks **-*/ - - /** - * Retrieve hooks added to object - * @return array Hooks - */ - function get_hooks() { - return $this->get_member_value('hooks', '', array()); - } - - /** - * Add hook for object - * @see add_filter() for parameter defaults - * @param $tag - * @param $function_to_add - * @param $priority - * @param $accepted_args - */ - function add_hook($tag, $function_to_add, $priority = 10, $accepted_args = 1) { - //Create new array for tag (if not already set) - if ( !isset($this->hooks[$tag]) ) - $this->hooks[$tag] = array(); - //Build Unique ID - if ( is_string($function_to_add) ) - $id = $function_to_add; - elseif ( is_array($function_to_add) && !empty($function_to_add) ) - $id = strval($function_to_add[count($function_to_add) - 1]); - else - $id = 'function_' . ( count($this->hooks[$tag]) + 1 ); - //Add hook - $this->hooks[$tag][$id] = func_get_args(); - } - - /** - * Convenience method for adding an action for object - * @see add_filter() for parameter defaults - * @param $tag - * @param $function_to_add - * @param $priority - * @param $accepted_args - */ - function add_action($tag, $function_to_add, $priority = 10, $accepted_args = 1) { - $this->add_hook($tag, $function_to_add, $priority, $accepted_args); - } - - /** - * Convenience method for adding a filter for object - * @see add_filter() for parameter defaults - * @param $tag - * @param $function_to_add - * @param $priority - * @param $accepted_args - */ - function add_filter($tag, $function_to_add, $priority = 10, $accepted_args = 1) { - $this->add_hook($tag, $function_to_add, $priority, $accepted_args); - } - - /*-** Dependencies **-*/ - - /** - * Adds dependency to object - * @param string $type Type of dependency to add (script, style) - * @param array|string $context When dependency will be added (@see CNR_Utilities::get_action() for possible contexts) - * @see wp_enqueue_script for the following of the parameters - * @param $handle - * @param $src - * @param $deps - * @param $ver - * @param $ex - */ - function add_dependency($type, $context, $handle, $src = false, $deps = array(), $ver = false, $ex = false) { - $args = func_get_args(); - //Remove type/context from arguments - $args = array_slice($args, 2); - - //Set context - if ( !is_array($context) ) { - //Wrap single contexts in an array - if ( is_string($context) ) - $context = array($context); - else - $context = array(); - } - //Add file to instance property - $this->{$type}[$handle] = array('context' => $context, 'params' => $args); - } - - /** - * Add script to object to be added in specified contexts - * @param array|string $context Array of contexts to add script to page - * @see wp_enqueue_script for the following of the parameters - * @param $handle - * @param $src - * @param $deps - * @param $ver - * @param $in_footer - */ - function add_script( $context, $handle, $src = false, $deps = array(), $ver = false, $in_footer = false ) { - $args = func_get_args(); - //Add file type to front of arguments array - array_unshift($args, 'scripts'); - call_user_func_array(array(&$this, 'add_dependency'), $args); - } - - /** - * Retrieve script dependencies for object - * @return array Script dependencies - */ - function get_scripts() { - return $this->get_member_value('scripts', '', array()); - } - - /** - * Add style to object to be added in specified contexts - * @param array|string $context Array of contexts to add style to page - * @see wp_enqueue_style for the following of the parameters - * @param $handle - * @param $src - * @param $deps - * @param $ver - * @param $in_footer - */ - function add_style( $handle, $src = false, $deps = array(), $ver = false, $media = false ) { - $args = func_get_args(); - array_unshift($args, 'styles'); - call_user_method_array('add_dependency', $this, $args); - } - - /** - * Retrieve Style dependencies for object - * @return array Style dependencies - */ - function get_styles() { - return $this->get_member_value('styles', '', array()); - } -} - -/** - * Content Type - Field Types - * Stores properties for a specific field - * @package Cornerstone - * @subpackage Content Types - * @author Archetyped - */ -class CNR_Field_Type extends CNR_Content_Base { - /* Properties */ - - const USES_DATA = '{data}'; - - /** - * Base class name - * @var string - */ - var $base_class = 'cnr_field_type'; - - /** - * @var array Array of Field types that make up current Field type - */ - var $elements = array(); - - /** - * Structure: Property names stored as keys in group - * Root - * -> Group Name - * -> Property Name => Null - * Reason: Faster searching over large arrays - * @var array Groupings of Properties - */ - var $property_groups = array(); - - /** - * @var array Field type layouts - */ - var $layout = array(); - - /** - * @var CNR_Field_Type Parent field type (reference) - */ - var $parent = null; - - /** - * Object that field is in - * @var CNR_Field|CNR_Field_Type|CNR_Content_Type - */ - var $container = null; - - /** - * Object that called field - * Used to determine field hierarchy/nesting - * @var CNR_Field|CNR_Field_Type|CNR_Content_Type - */ - var $caller = null; - - /** - * Constructor - */ - function __construct($id = '', $parent = null) { - parent::__construct($id); - - $this->id = $id; - $this->set_parent($parent); - } - - /* Getters/Setters */ - - /** - * Search for specified member value in field's container object (if exists) - * @param string $member Name of object member to search (e.g. properties, layout, etc.) - * @param string $name Value to retrieve from member - * @return mixed Member value if found (Default: empty string) - */ - function get_container_value($member, $name = '', $default = '') { - $container =& $this->get_container(); - return $this->get_object_value($container, $member, $name, $default, 'container'); - } - - /** - * Search for specified member value in field's container object (if exists) - * @param string $member Name of object member to search (e.g. properties, layout, etc.) - * @param string $name Value to retrieve from member - * @return mixed Member value if found (Default: empty string) - */ - function get_caller_value($member, $name = '', $default = '') { - $caller =& $this->get_caller(); - return $this->get_object_value($caller, $member, $name, $default, 'caller'); - } - - /** - * Sets reference to container object of current field - * Reference is cleared if no valid object is passed to method - * @param object $container - */ - function set_container(&$container) { - if ( !empty($container) && is_object($container) ) { - //Set as param as container for current field - $this->container =& $container; - } else { - //Clear container member if argument is invalid - $this->clear_container(); - } - } - - /** - * Clears reference to container object of current field - */ - function clear_container() { - $this->container = null; - } - - /** - * Retrieves reference to container object of current field - * @return object Reference to container object - */ - function &get_container() { - $ret = null; - if ( $this->has_container() ) - $ret =& $this->container; - return $ret; - } - - /** - * Checks if field has a container reference - * @return bool TRUE if field is contained, FALSE otherwise - */ - function has_container() { - return !empty($this->container); - } - - /** - * Sets reference to calling object of current field - * Any existing reference is cleared if no valid object is passed to method - * @param object $caller Calling object - */ - function set_caller(&$caller) { - if ( !empty($caller) && is_object($caller) ) - $this->caller =& $caller; - else - $this->clear_caller(); - } - - /** - * Clears reference to calling object of current field - */ - function clear_caller() { - unset($this->caller); - } - - /** - * Retrieves reference to caller object of current field - * @return object Reference to caller object - */ - function &get_caller() { - $ret = null; - if ( $this->has_caller() ) - $ret =& $this->caller; - return $ret; - } - - /** - * Checks if field has a caller reference - * @return bool TRUE if field is called by another field, FALSE otherwise - */ - function has_caller() { - return !empty($this->caller); - } - - /** - * Add/Set a property on the field definition - * @param string $name Name of property - * @param mixed $value Default value for property - * @param string|array $group Group(s) property belongs to - * @return boolean TRUE if property is successfully added to field type, FALSE otherwise - */ - function set_property($name, $value = '', $group = null) { - //Do not add if property name is not a string - if ( !is_string($name) ) - return false; - //Create property array - $prop_arr = array(); - $prop_arr['value'] = $value; - //Add to properties array - $this->properties[$name] = $value; - //Add property to specified groups - if ( !empty($group) ) { - $this->set_group_property($group, $name); - } - return true; - } - - /** - * Sets multiple properties on field type at once - * @param array $properties Properties. Each element is an array containing the arguments to set a new property - * @return boolean TRUE if successful, FALSE otherwise - */ - function set_properties($properties) { - if ( !is_array($properties) ) - return false; - foreach ( $properties as $name => $val) { - $this->set_property($name, $val); - } - } - - /** - * Retreives property from field type - * @param string $name Name of property to retrieve - * @return mixed Specified Property if exists (Default: Empty string) - */ - function get_property($name) { - $val = $this->get_member_value('properties', $name); - return $val; - } - - /** - * Adds Specified Property to a Group - * @param string|array $group Group(s) to add property to - * @param string $property Property to add to group - */ - function set_group_property($group, $property) { - if ( is_string($group) && isset($this->property_groups[$group][$property]) ) - return; - if ( !is_array($group) ) { - $group = array($group); - } - - foreach ($group as $g) { - $g = trim($g); - //Initialize group if it doesn't already exist - if ( !isset($this->property_groups[$g]) ) - $this->property_groups[$g] = array(); - - //Add property to group - $this->property_groups[$g][$property] = null; - } - } - - /** - * Retrieve property group - * @param string $group Group to retrieve - * @return array Array of properties in specified group - */ - function get_group($group) { - return $this->get_member_value('property_groups', $group, array()); - } - - /** - * Sets an element for the field type - * @param string $name Name of element - * @param CNR_Field_Type $type Reference of field type to use for element - * @param array $properties Properties for element (passed as keyed associative array) - * @param string $id_prop Name of property to set $name to (e.g. ID, etc.) - */ - function set_element($name, $type, $properties = array(), $id_prop = 'id') { - $name = trim(strval($name)); - if ( empty($name) ) - return false; - //Create new field for element - $el = new CNR_Field($name, $type); - //Set container to current field instance - $el->set_container($this); - //Add properties to element - $el->set_properties($properties); - //Save element to current instance - $this->elements[$name] =& $el; - } - - /** - * Add a layout to the field - * @param string $name Name of layout - * @param string $value Layout text - */ - function set_layout($name, $value = '') { - if ( !is_string($name) ) - return false; - $name = trim($name); - $this->layout[$name] = $value; - return true; - } - - /** - * Retrieve specified layout - * @param string $name Layout name - * @param bool $parse_nested (optional) Whether nested layouts should be expanded in retreived layout or not (Default: TRUE) - * @return string Specified layout text - */ - function get_layout($name = 'form', $parse_nested = true) { - //Retrieve specified layout (use $name value if no layout by that name exists) - $layout = $this->get_member_value('layout', $name, $name); - - //Find all nested layouts in current layout - if ( !empty($layout) && !!$parse_nested ) { - $ph = $this->get_placeholder_defaults(); - - while ($ph->match = $this->parse_layout($layout, $ph->pattern_layout)) { - //Iterate through the different types of layout placeholders - foreach ($ph->match as $tag => $instances) { - //Iterate through instances of a specific type of layout placeholder - foreach ($instances as $instance) { - //Get nested layout - $nested_layout = $this->get_member_value($instance); - - //Replace layout placeholder with retrieved item data - if ( !empty($nested_layout) ) - $layout = str_replace($ph->start . $instance['match'] . $ph->end, $nested_layout, $layout); - } - } - } - } - - return $layout; - } - - /** - * Checks if specified layout exists - * Finds layout if it exists in current object or any of its parents - * @param string $layout Name of layout to check for - * @return bool TRUE if layout exists, FALSE otherwise - */ - function has_layout($layout) { - $ret = false; - if ( is_string($layout) && ($layout = trim($layout)) && !empty($layout) ) { - $layout = $this->get_member_value('layout', $layout, false); - if ( $layout !== false ) - $ret = true; - } - - return $ret; - } - - /** - * Checks if layout content is valid - * Layouts need to have placeholders to be valid - * @param string $layout_content Layout content (markup) - * @return bool TRUE if layout is valid, FALSE otherwise - */ - function is_valid_layout($layout_content) { - $ph = $this->get_placeholder_defaults(); - return preg_match($ph->pattern_general, $layout_content); - } - - /** - * Parse field layout with a regular expression - * @param string $layout Layout data - * @param string $search Regular expression pattern to search layout for - * @return array Associative array containing all of the regular expression matches in the layout data - * Array Structure: - * root => placeholder tags - * => Tag instances (array) - * 'tag' => (string) tag name - * 'match' => (string) placeholder match - * 'attributes' => (array) attributes - */ - function parse_layout($layout, $search) { - $ph_xml = ''; - $parse_match = ''; - $ph_root_tag = 'ph_root_element'; - $ph_start_xml = '<'; - $ph_end_xml = ' />'; - $ph_wrap_start = '<' . $ph_root_tag . '>'; - $ph_wrap_end = ''; - $parse_result = false; - - //Find all nested layouts in layout - $match_value = preg_match_all($search, $layout, $parse_match, PREG_PATTERN_ORDER); - - if ($match_value !== false && $match_value > 0) { - $parse_result = array(); - //Get all matched elements - $parse_match = $parse_match[1]; - - //Build XML string from placeholders - foreach ($parse_match as $ph) { - $ph_xml .= $ph_start_xml . $ph . $ph_end_xml . ' '; - } - $ph_xml = $ph_wrap_start . $ph_xml . $ph_wrap_end; - //Parse XML data - $ph_prs = xml_parser_create(); - xml_parser_set_option($ph_prs, XML_OPTION_SKIP_WHITE, 1); - xml_parser_set_option($ph_prs, XML_OPTION_CASE_FOLDING, 0); - $ret = xml_parse_into_struct($ph_prs, $ph_xml, $parse_result['values'], $parse_result['index']); - xml_parser_free($ph_prs); - - //Build structured array with all parsed data - - unset($parse_result['index'][$ph_root_tag]); - - //Build structured array - $result = array(); - foreach ($parse_result['index'] as $tag => $instances) { - $result[$tag] = array(); - //Instances - foreach ($instances as $instance) { - //Skip instance if it doesn't exist in parse results - if (!isset($parse_result['values'][$instance])) - continue; - - //Stop processing instance if a previously-saved instance with the same options already exists - foreach ($result[$tag] as $tag_match) { - if ($tag_match['match'] == $parse_match[$instance - 1]) - continue 2; - } - - //Init instance data array - $inst_data = array(); - - //Add Tag to array - $inst_data['tag'] = $parse_result['values'][$instance]['tag']; - - //Add instance data to array - $inst_data['attributes'] = (isset($parse_result['values'][$instance]['attributes'])) ? $inst_data['attributes'] = $parse_result['values'][$instance]['attributes'] : ''; - - //Add match to array - $inst_data['match'] = $parse_match[$instance - 1]; - - //Add to result array - $result[$tag][] = $inst_data; - } - } - $parse_result = $result; - } - - return $parse_result; - } - - /** - * Retrieves default properties to use when evaluating layout placeholders - * @return object Object with properties for evaluating layout placeholders - */ - function get_placeholder_defaults() { - $ph = new stdClass(); - $ph->start = '{'; - $ph->end = '}'; - $ph->reserved = array('ref' => 'ref_base'); - $ph->pattern_general = '/' . $ph->start . '([a-zA-Z0-9_].*?)' . $ph->end . '/i'; - $ph->pattern_layout = '/' . $ph->start . '([a-zA-Z0-9].*?\s+' . $ph->reserved['ref'] . '="layout.*?".*?)' . $ph->end . '/i'; - return $ph; - } - - /** - * Builds HTML for a field based on its properties - * @param array $field Field properties (id, field, etc.) - * @param array data Additional data for current field - */ - function build_layout($layout = 'form', $data = null) { - $out_default = ''; - - /* Layout */ - - //Get base layout - $out = $this->get_layout($layout); - - //Only parse valid layouts - if ( $this->is_valid_layout($out) ) { - //Parse Layout - $ph = $this->get_placeholder_defaults(); - - //Search layout for placeholders - while ( $ph->match = $this->parse_layout($out, $ph->pattern_general) ) { - //Iterate through placeholders (tag, id, etc.) - foreach ( $ph->match as $tag => $instances ) { - //Iterate through instances of current placeholder - foreach ( $instances as $instance ) { - //Process value based on placeholder name - $target_property = apply_filters('cnr_process_placeholder_' . $tag, '', $this, $instance, $layout, $data); - - //Process value using default processors (if necessary) - if ( '' == $target_property ) { - $target_property = apply_filters('cnr_process_placeholder', $target_property, $this, $instance, $layout, $data); - } - - //Clear value if value not a string - if ( !is_scalar($target_property) ) { - $target_property = ''; - } - //Replace layout placeholder with retrieved item data - $out = str_replace($ph->start . $instance['match'] . $ph->end, $target_property, $out); - } - } - } - } else { - $out = $out_default; - } - - /* Return generated value */ - - return $out; - } - - /*-** Static Methods **-*/ - - /** - * Returns indacator to use field data (in layouts, property values, etc.) - */ - function uses_data() { - return self::USES_DATA; - } - - /** - * Register a function to handle a placeholder - * Multiple handlers may be registered for a single placeholder - * Basically a wrapper function to facilitate adding hooks for placeholder processing - * @uses add_filter() - * @param string $placeholder Name of placeholder to add handler for (Using 'all' will set the function as a handler for all placeholders - * @param callback $handler Function to set as a handler - * @param int $priority (optional) Priority of handler - */ - static function register_placeholder_handler($placeholder, $handler, $priority = 10) { - if ( 'all' == $placeholder ) - $placeholder = ''; - else - $placeholder = '_' . $placeholder; - - add_filter('cnr_process_placeholder' . $placeholder, $handler, $priority, 5); - } - - /** - * Default placeholder processing - * To be executed when current placeholder has not been handled by another handler - * @param string $ph_output Value to be used in place of placeholder - * @param CNR_Field $field Field containing placeholder - * @param array $placeholder Current placeholder - * @see CNR_Field::parse_layout for structure of $placeholder array - * @param string $layout Layout to build - * @param array $data Extended data for field - * @return string Value to use in place of current placeholder - */ - function process_placeholder_default($ph_output, $field, $placeholder, $layout, $data) { - //Validate parameters before processing - if ( empty($ph_output) && is_a($field, 'CNR_Field_Type') && is_array($placeholder) ) { - //Build path to replacement data - $ph_output = $field->get_member_value($placeholder); - - //Check if value is group (properties, etc.) - //All groups must have additional attributes (beyond reserved attributes) that define how items in group are used - if (is_array($ph_output) - && !empty($placeholder['attributes']) - && is_array($placeholder['attributes']) - && ($ph = $field->get_placeholder_defaults()) - && $attribs = array_diff(array_keys($placeholder['attributes']), array_values($ph->reserved)) - ) { - /* Targeted property is an array, but the placeholder contains additional options on how property is to be used */ - - //Find items matching criteria in $ph_output - //Check for group criteria - //TODO: Implement more robust/flexible criteria handling (2010-03-11: Currently only processes property groups) - if ( 'properties' == $placeholder['tag'] && ($prop_group = $field->get_group($placeholder['attributes']['group'])) && !empty($prop_group) ) { - /* Process group */ - $group_out = array(); - //Iterate through properties in group and build string - foreach ( $prop_group as $prop_key => $prop_val ) { - $group_out[] = $prop_key . '="' . $field->get_property($prop_key) . '"'; - } - $ph_output = implode(' ', $group_out); - } - } elseif ( is_object($ph_output) && is_a($ph_output, $field->base_class) ) { - /* Targeted property is actually a nested field */ - //Set caller to current field - $ph_output->set_caller($field); - //Build layout for nested element - $ph_output = $ph_output->build_layout($layout); - } - } - - return $ph_output; - } - - /** - * Build Field ID attribute - * @see CNR_Field_Type::process_placeholder_default for parameter descriptions - * @return string Placeholder output - */ - function process_placeholder_id($ph_output, $field, $placeholder, $layout, $data) { - //Get attributes - $args = wp_parse_args($placeholder['attributes'], array('format' => 'attr_id')); - return $field->get_id($args); - } - - /** - * Build Field name attribute - * Name is formatted as an associative array for processing by PHP after submission - * @see CNR_Field_Type::process_placeholder_default for parameter descriptions - * @return string Placeholder output - */ - function process_placeholder_name($ph_output, $field, $placeholder, $layout, $data) { - //Get attributes - $args = wp_parse_args($placeholder['attributes'], array('format' => 'default')); - return $field->get_id($args); - } - - /** - * Retrieve data for field - * @see CNR_Field_Type::process_placeholder_default for parameter descriptions - * @return string Placeholder output - */ - function process_placeholder_data($ph_output, $field, $placeholder, $layout) { - $val = $field->get_data(); - if ( !is_null($val) ) { - $ph_output = $val; - $attr =& $placeholder['attributes']; - //Get specific member in value (e.g. value from a specific field element) - if ( isset($attr['element']) && is_array($ph_output) && ( $el = $attr['element'] ) && isset($ph_output[$el]) ) - $ph_output = $ph_output[$el]; - if ( isset($attr['format']) && 'display' == $attr['format'] ) - $ph_output = nl2br($ph_output); - } - - //Return data - return $ph_output; - } - - /** - * Loops over data to build field output - * Options: - * data - Dot-delimited path in field that contains data to loop through - * layout - Name of layout to use for each data item in loop - * layout_data - Name of layout to use for data item that matches previously-saved field data - * @see CNR_Field_Type::process_placeholder_default for parameter descriptions - * @return string Placeholder output - */ - function process_placeholder_loop($ph_output, $field, $placeholder, $layout, $data) { - //Setup loop options - $attr_defaults = array ( - 'layout' => '', - 'layout_data' => null, - 'data' => '' - ); - - $attr = wp_parse_args($placeholder['attributes'], $attr_defaults); - - if ( is_null($attr['layout_data']) ) { - $attr['layout_data'] =& $attr['layout']; - } - - //Get data for loop - $path = explode('.', $attr['data']); - $loop_data = $field->get_member_value($path); - /*if ( isset($loop_data['value']) ) - $loop_data = $loop_data['value']; - */ - $out = array(); - - //Get field data - $data = $field->get_data(); - - //Iterate over data and build output - if ( is_array($loop_data) && !empty($loop_data) ) { - foreach ( $loop_data as $value => $label ) { - //Load appropriate layout based on field value - $layout = ( ($data === 0 && $value === $data) xor $data == $value ) ? $attr['layout_data'] : $attr['layout']; - //Stop processing if no valid layout is returned - if ( empty($layout) ) - continue; - //Prep extended field data - $data_ext = array('option_value' => $value, 'option_text' => $label); - $out[] = $field->build_layout($layout, $data_ext); - } - } - - //Return output - return implode($out); - } - - /** - * Returns specified value from extended data array for field - * @see CNR_Field_Type::process_placeholder_default for parameter descriptions - * @return string Placeholder output - */ - function process_placeholder_data_ext($ph_output, $field, $placeholder, $layout, $data) { - if ( isset($placeholder['attributes']['id']) && ($key = $placeholder['attributes']['id']) && isset($data[$key]) ) { - $ph_output = strval($data[$key]); - } - - return $ph_output; - } - - /** - * WP Editor - * @see CNR_Field_Type::process_placeholder_default for parameter descriptions - * @return string Placeholder output - */ - function process_placeholder_rich_editor($ph_output, $field, $placeholder, $layout, $data) { - $id = $field->get_id( array ( - 'format' => 'attr_id' - )); - $settings = array ( - 'textarea_name' => $field->get_id( array ( - 'format' => 'default' - )) - ); - ob_start(); - wp_editor($field->get_data(), $id, $settings); - $out = ob_get_clean(); - return $out; - } - -} - -class CNR_Field extends CNR_Field_Type { - -} - -class CNR_Content_Type extends CNR_Content_Base { - - /** - * Base class for instance objects - * @var string - */ - var $base_class = 'cnr_content_type'; - - /** - * Indexed array of fields in content type - * @var array - */ - var $fields = array(); - - /** - * Associative array of groups in conten type - * Key: Group name - * Value: object of group properties - * > description string Group description - * > location string Location of group on edit form - * > fields array Fields in group - * @var array - */ - var $groups = array(); - - /* Constructors */ - - /** - * Class constructor - * @param string $id Content type ID - * @param string|bool $parent (optional) Parent to inherit properties from (Default: none) - * @param array $properties (optional) Properties to set for content type (Default: none) - */ - function __construct($id = '', $parent = null, $properties = null) { - parent::__construct($id, $parent); - - //Set properties - //TODO Iterate through additional arguments and set instance properties - } - - /* Registration */ - - /** - * Registers current content type w/CNR - */ - function register() { - global $cnr_content_utilities; - $cnr_content_utilities->register_content_type($this); - } - - /* Getters/Setters */ - - /** - * Adds group to content type - * Groups are used to display related fields in the UI - * @param string $id Unique name for group - * @param string $title Group title - * @param string $description Short description of group's purpose - * @param string $location Where group will be displayed on post edit form (Default: main) - * @param array $fields (optional) ID's of existing fields to add to group - * @return object Group object - */ - function &add_group($id, $title = '', $description = '', $location = 'normal', $fields = array()) { - //Create new group and set properties - $id = trim($id); - $this->groups[$id] =& $this->create_group($title, $description, $location); - //Add fields to group (if supplied) - if ( !empty($fields) && is_array($fields) ) - $this->add_to_group($id, $fields); - return $this->groups[$id]; - } - - /** - * Remove specified group from content type - * @param string $id Group ID to remove - */ - function remove_group($id) { - $id = trim($id); - if ( $this->group_exists($id) ) { - unset($this->groups[$id]); - } - } - - /** - * Standardized method to create a new field group - * @param string $title Group title (used in meta boxes, etc.) - * @param string $description Short description of group's purpose - * @param string $location Where group will be displayed on post edit form (Default: main) - * @return object Group object - */ - function &create_group($title = '', $description = '', $location = 'normal') { - $group = new stdClass(); - $title = ( is_scalar($title) ) ? trim($title) : ''; - $group->title = $title; - $description = ( is_scalar($description) ) ? trim($description) : ''; - $group->description = $description; - $location = ( is_scalar($location) ) ? trim($location) : 'normal'; - $group->location = $location; - $group->fields = array(); - return $group; - } - - /** - * Checks if group exists - * @param string $id Group name - * @return bool TRUE if group exists, FALSE otherwise - */ - function group_exists($id) { - $id = trim($id); - //Check if group exists in content type - return ( !is_null($this->get_member_value('groups', $id, null)) ); - } - - /** - * Adds field to content type - * @param string $id Unique name for field - * @param CNR_Field_Type|string $parent Field type that this field is based on - * @param array $properties (optional) Field properties - * @param string $group (optional) Group ID to add field to - * @return CNR_Field Reference to new field - */ - function &add_field($id, $parent, $properties = array(), $group = null) { - //Create new field - $id = trim(strval($id)); - $field = new CNR_Field($id); - $field->set_parent($parent); - $field->set_container($this); - $field->set_properties($properties); - - //Add field to content type - $this->fields[$id] =& $field; - //Add field to group - $this->add_to_group($group, $field->id); - return $field; - } - - /** - * Removes field from content type - * @param string|CNR_Field $field Object or Field ID to remove - */ - function remove_field($field) { - $field = CNR_Field_Type::get_id($field); - if ( !$field ) - return false; - - //Remove from fields array - //$this->fields[$field] = null; - unset($this->fields[$field]); - - //Remove field from groups - $this->remove_from_group($field); - } - - /** - * Retrieve specified field in Content Type - * @param string $field Field ID - * @return CNR_Field Specified field - */ - function &get_field($field) { - if ( $this->has_field($field) ) { - $field = trim($field); - $field = $this->get_member_value('fields', $field); - } else { - //Return empty field if no field exists - $field = new CNR_Field(''); - } - return $field; - } - - /** - * Checks if field exists in the content type - * @param string $field Field ID - * @return bool TRUE if field exists, FALSE otherwise - */ - function has_field($field) { - return ( !is_string($field) || empty($field) || is_null($this->get_member_value('fields', $field, null)) ) ? false : true; - } - - /** - * Adds field to a group in the content type - * Group is created if it does not already exist - * @param string|array $group ID of group (or group parameters if new group) to add field to - * @param string|array $fields Name or array of field(s) to add to group - */ - function add_to_group($group, $fields) { - //Validate parameters - $group_id = ''; - if ( !empty($group) ) { - if ( !is_array($group) ) { - $group = array($group, $group); - } - - $group[0] = $group_id = trim(sanitize_title_with_dashes($group[0])); - } - if ( empty($group_id) || empty($fields) ) - return false; - //Create group if it doesn't exist - if ( !$this->group_exists($group_id) ) { - call_user_func_array($this->m('add_group'), $group); - } - if ( ! is_array($fields) ) - $fields = array($fields); - foreach ( $fields as $field ) { - unset($fref); - if ( ! $this->has_field($field) ) - continue; - $fref =& $this->get_field($field); - //Remove field from any other group it's in (fields can only be in one group) - foreach ( array_keys($this->groups) as $group_name ) { - if ( isset($this->groups[$group_name]->fields[$fref->id]) ) - unset($this->groups[$group_name]->fields[$fref->id]); - } - //Add reference to field in group - $this->groups[$group_id]->fields[$fref->id] =& $fref; - } - } - - /** - * Remove field from a group - * If no group is specified, then field is removed from all groups - * @param string|CNR_Field $field Field object or ID of field to remove from group - * @param string $group (optional) Group ID to remove field from - */ - function remove_from_group($field, $group = '') { - //Get ID of field to remove or stop execution if field invalid - $field = CNR_Field_Type::get_id($field); - if ( !$field ) - return false; - - //Remove field from group - if ( !empty($group) ) { - //Remove field from single group - if ( ($group =& $this->get_group($group)) && isset($group->fields[$field]) ) { - unset($group->fields[$field]); - } - } else { - //Remove field from all groups - foreach ( array_keys($this->groups) as $group ) { - if ( ($group =& $this->get_group($group)) && isset($group->fields[$field]) ) { - unset($group->fields[$field]); - } - } - } - } - - /** - * Retrieve specified group - * @param string $group ID of group to retrieve - * @return object Reference to specified group - */ - function &get_group($group) { - $group = trim($group); - //Create group if it doesn't already exist - if ( ! $this->group_exists($group) ) - $this->add_group($group); - $group = $this->get_member_value('groups', $group); - return $group; - } - - /** - * Retrieve all groups in content type - * @return array Reference to group objects - */ - function &get_groups() { - $groups = $this->get_member_value('groups'); - return $groups; - } - - /** - * Output fields in a group - * @param string $group ID of Group to output - * @return string Group output - */ - function build_group($group) { - $out = array(); - $classnames = (object) array( - 'multi' => 'multi_field', - 'single' => 'single_field', - 'elements' => 'has_elements' - ); - - //Stop execution if group does not exist - if ( $this->group_exists($group) && $group =& $this->get_group($group) ) { - $group_fields = ( count($group->fields) > 1 ) ? $classnames->multi : $classnames->single . ( ( ( $fs = array_keys($group->fields) ) && ( $f =& $group->fields[$fs[0]] ) && ( $els = $f->get_member_value('elements', '', null) ) && !empty($els) ) ? '_' . $classnames->elements : '' ); - $classname = array('cnr_attributes_wrap', $group_fields); - $out[] = '
'; //Wrap all fields in group - - //Build layout for each field in group - foreach ( array_keys($group->fields) as $field_id ) { - /** - * CNR_Field_Type - */ - $field =& $group->fields[$field_id]; - $field->set_caller($this); - //Start field output - $id = 'cnr_field_' . $field->get_id(); - $class = array('cnr_attribute_wrap'); - //If single field in group, check if field title matches group - if ( count($group->fields) == 1 && $group->title == $field->get_property('label') ) - $class[] = 'group_field_title'; - //Add flag to indicate that field was loaded on page - $inc = 'cnr[fields_loaded][' . $field->get_id() . ']'; - $out[] = ''; - $out[] = '
'; - //Build field layout - $out[] = $field->build_layout(); - //end field output - $out[] = '
'; - $field->clear_caller(); - } - $out[] = '
'; //Close fields container - //Add description if exists - if ( !empty($group->description) ) - $out[] = '

' . $group->description . '

'; - } - - //Return group output - return implode($out); - } - - /** - * Set data for a field - * @param string|CNR_Field $field Reference or ID of Field to set data for - * @param mixed $value Data to set - */ - function set_data($field, $value = '') { - if ( 1 == func_num_args() && is_array($field) ) - $this->data = $field; - else { - $field = CNR_Field_Type::get_id($field); - if ( empty($field) ) - return false; - $this->data[$field] = $value; - } - } - - /*-** Admin **-*/ - - /** - * Adds meta boxes for post's content type - * Each group in content type is a separate meta box - * @param string $type Type of item meta boxes are being build for (post, page, link) - * @param string $context Location of meta box (normal, advanced, side) - * @param object $post Post object - */ - function admin_do_meta_boxes($type, $context, $post) { - //Add post data to content type - global $cnr_content_utilities; - $this->set_data($cnr_content_utilities->get_item_data($post)); - - //Get Groups - $groups = array_keys($this->get_groups()); - $priority = 'default'; - //Iterate through groups and add meta box if it fits the context (location) - foreach ( $groups as $group_id ) { - $group =& $this->get_group($group_id); - if ( $context == $group->location && count($group->fields) ) { - //Format ID for meta box - $meta_box_id = $this->prefix . '_group_' . $group_id; - $group_args = array( 'group' => $group_id ); - add_meta_box($meta_box_id, $group->title, $this->m('admin_build_meta_box'), $type, $context, $priority, $group_args); - } - } - } - - /** - * Outputs group fields for a meta box - * @param object $post Post object - * @param array $box Meta box properties - */ - function admin_build_meta_box($post, $box) { - //Stop execution if group not specified - if ( !isset($box['args']['group']) ) - return false; - - //Get ID of group to output - $group_id =& $box['args']['group']; - - $output = array(); - $output[] = '
'; - $output[] = $this->build_group($group_id); - $output[] = '
'; - - //Output group content to screen - echo implode($output); - } - - /** - * Retrieves type ID formatted as a meta value - * @return string - */ - function get_meta_value() { - return serialize(array($this->id)); - } - -} - -/** - * Utilities for Content Type functionality - * @package Cornerstone - * @subpackage Content Types - * @author Archetyped - */ -class CNR_Content_Utilities extends CNR_Base { - - /** - * Array of hooks called - * @var array - */ - var $hooks_processed = array(); - - /** - * Initialize content type functionality - */ - function init() { - $this->register_hooks(); - } - - /** - * Registers hooks for content types - * @todo 2010-07-30: Check hooks for 3.0 compatibility - */ - function register_hooks() { - //Register types - add_action('init', $this->m('register_types')); - add_action('init', $this->m('add_hooks'), 11); - - //Enqueue scripts for fields in current post type - add_action('admin_enqueue_scripts', $this->m('enqueue_files')); - - //Add menus - //add_action('admin_menu', $this->m('admin_menu')); - - //Build UI on post edit form - add_action('do_meta_boxes', $this->m('admin_do_meta_boxes'), 10, 3); - - //Get edit link for items - //add_filter('get_edit_post_link', $this->m('get_edit_item_url'), 10, 3); - - //add_action('edit_form_advanced', $this->m('admin_page_edit_form')); - - //Save Field data/Content type - add_action('save_post', $this->m('save_item_data'), 10, 2); - - //Modify post query for content type compatibility - add_action('pre_get_posts', $this->m('pre_get_posts'), 20); - } - - /** - * Initialize fields and content types - */ - function register_types() { - //Global variables - global $cnr_field_types, $cnr_content_types; - - /* Field Types */ - - //Base - $base = new CNR_Field_Type('base'); - $base->set_description('Default Element'); - $base->set_property('tag', 'span'); - $base->set_property('class', '', 'attr'); - $base->set_layout('form', '<{tag} name="{field_name}" id="{field_id}" {properties ref_base="root" group="attr"} />'); - $base->set_layout('label', ''); - $base->set_layout('display', '{data format="display"}'); - $this->register_field($base); - - //Base closed - $base_closed = new CNR_Field_Type('base_closed'); - $base_closed->set_parent('base'); - $base_closed->set_description('Default Element (Closed Tag)'); - $base_closed->set_layout('form_start', '<{tag} id="{field_id}" name="{field_name}" {properties ref_base="root" group="attr"}>'); - $base_closed->set_layout('form_end', ''); - $base_closed->set_layout('form', '{form_start ref_base="layout"}{data}{form_end ref_base="layout"}'); - $this->register_field($base_closed); - - //Input - $input = new CNR_Field_Type('input'); - $input->set_parent('base'); - $input->set_description('Default Input Element'); - $input->set_property('tag', 'input'); - $input->set_property('type', 'text', 'attr'); - $input->set_property('value', CNR_Field::USES_DATA, 'attr'); - $this->register_field($input); - - //Text input - $text = new CNR_Field_Type('text', 'input'); - $text->set_description('Text Box'); - $text->set_property('size', 15, 'attr'); - $text->set_property('label'); - $text->set_layout('form', '{label ref_base="layout"} {inherit}'); - $this->register_field($text); - - //Checkbox - $checkbox = new CNR_Field_Type('checkbox', 'input'); - $checkbox->set_description('Checkbox'); - $checkbox->set_property('type', 'checkbox', 'attr'); - $checkbox->set_property('label'); - $checkbox->set_property('checked', '', 'attr'); - $checkbox->set_layout('form', '{inherit} {label ref_base="layout"}'); - $this->register_field($checkbox); - - //Textarea - $ta = new CNR_Field_Type('textarea', 'base_closed'); - $ta->set_property('tag', 'textarea'); - $ta->set_property('cols', 40, 'attr'); - $ta->set_property('rows', 3, 'attr'); - $this->register_field($ta); - - //Rich Text - $rt = new CNR_Field_Type('richtext', 'textarea'); - $rt->set_layout('form', '
{rich_editor}
'); - $this->register_field($rt); - - //Location - $location = new CNR_Field_Type('location'); - $location->set_description('Geographic Coordinates'); - $location->set_element('latitude', 'text', array( 'size' => 3, 'label' => 'Latitude' )); - $location->set_element('longitude', 'text', array( 'size' => 3, 'label' => 'Longitude' )); - $location->set_layout('form', '{latitude ref_base="elements"}, {longitude ref_base="elements"}'); - $this->register_field($location); - - //Phone - $phone = new CNR_Field_Type('phone'); - $phone->set_description('Phone Number'); - $phone->set_element('area', 'text', array( 'size' => 3 )); - $phone->set_element('prefix', 'text', array( 'size' => 3 )); - $phone->set_element('suffix', 'text', array( 'size' => 4 )); - $phone->set_layout('form', '({area ref_base="elements"}) {prefix ref_base="elements"} - {suffix ref_base="elements"}'); - $this->register_field($phone); - - //Hidden - $hidden = new CNR_Field_Type('hidden'); - $hidden->set_parent('input'); - $hidden->set_description('Hidden Field'); - $hidden->set_property('type', 'hidden'); - $this->register_field($hidden); - - //Span - $span = new CNR_Field_Type('span'); - $span->set_description('Inline wrapper'); - $span->set_parent('base_closed'); - $span->set_property('tag', 'span'); - $span->set_property('value', 'Hello there!'); - $this->register_field($span); - - //Select - $select = new CNR_Field_Type('select'); - $select->set_description('Select tag'); - $select->set_parent('base_closed'); - $select->set_property('tag', 'select'); - $select->set_property('tag_option', 'option'); - $select->set_property('options', array()); - $select->set_layout('form', '{label ref_base="layout"} {form_start ref_base="layout"}{loop data="properties.options" layout="option" layout_data="option_data"}{form_end ref_base="layout"}'); - $select->set_layout('option', '<{tag_option} value="{data_ext id="option_value"}">{data_ext id="option_text"}'); - $select->set_layout('option_data', '<{tag_option} value="{data_ext id="option_value"}" selected="selected">{data_ext id="option_text"}'); - $this->register_field($select); - - //Enable plugins to modify (add, remove, etc.) field types - do_action_ref_array('cnr_register_field_types', array(&$cnr_field_types)); - - //Content Types - - //Enable plugins to add/remove content types - do_action_ref_array('cnr_register_content_types', array(&$cnr_content_types)); - - //Enable plugins to modify content types after they have all been registered - do_action_ref_array('cnr_content_types_registered', array(&$cnr_content_types)); - } - - /** - * Add content type to global array of content types - * @param CNR_Content_Type $ct Content type to register - * @global array $cnr_content_types Content types array - */ - function register_content_type(&$ct) { - //Add content type to CNR array - if ( $this->is_content_type($ct) && !empty($ct->id) ) { - global $cnr_content_types; - $cnr_content_types[$ct->id] =& $ct; - } - //WP Post Type Registration - global $wp_post_types; - if ( !empty($ct->id) && !isset($wp_post_types[$ct->id]) ) - register_post_type($ct->id, $this->build_post_type_args($ct)); - } - - /** - * Generates arguments array for WP Post Type Registration - * @param CNR_Content_Type $ct Content type being registered - * @return array Arguments array - * @todo Enable custom taxonomies - */ - function build_post_type_args(&$ct) { - //Setup labels - - //Build labels - $labels = array ( - 'name' => _( $ct->get_title(true) ), - 'singular_name' => _( $ct->get_title(false) ), - 'all_items' => sprintf( _( 'All %s' ), $ct->get_title(true) ), - ); - - //Action labels - $item_actions = array( - 'add_new' => 'Add New %s', - 'edit' => 'Edit %s', - 'new' => 'New %s', - 'view' => 'View %s', - 'search' => array('Search %s', true), - 'not_found' => array('No %s found', true, false), - 'not_found_in_trash' => array('No %s found in Trash', true, false) - ); - - foreach ( $item_actions as $key => $val ) { - $excluded = false; - $plural = false; - if ( is_array($val) ) { - if ( count($val) > 1 && true == $val[1] ) { - $plural = true; - } - if ( count($val) > 2 && false == $val[2] ) - $excluded = true; - $val = $val[0]; - } - $title = ( $plural ) ? $labels['name'] : $labels['singular_name']; - if ( $excluded ) - $item = $key; - else { - $item = $key . '_item' . ( ( $plural ) ? 's' : '' ); - } - $labels[$item] = sprintf($val, $title); - } - - //Setup args - $args = array ( - 'labels' => $labels, - 'description' => $ct->get_description(), - 'public' => true, - 'capability_type' => 'post', - 'rewrite' => array( 'slug' => strtolower($labels['name']) ), - 'has_archive' => true, - 'hierarchical' => false, - 'menu_position' => 5, - 'supports' => array('title', 'editor', 'author', 'thumbnail', 'excerpt', 'trackbacks', 'custom-fields', 'comments', 'revisions'), - 'taxonomies' => get_object_taxonomies('post'), - ); - - return $args; - } - - /** - * Add field type to global array of field types - * @param CNR_Field_Type $field Field to register - * - * @global array $cnr_field_types Field types array - */ - function register_field(&$field) { - if ( $this->is_field($field) && !empty($field->id) ) { - global $cnr_field_types; - $cnr_field_types[$field->id] =& $field; - } - } - - /*-** Helpers **-*/ - - /** - * Checks whether an object is a valid content type instance - * @param obj $ct Object to evaluate - * @return bool TRUE if object is a valid content type instance, FALSE otherwise - */ - function is_content_type(&$ct) { - return is_a($ct, 'cnr_content_type'); - } - - /** - * Checks whether an object is a valid field instance - * @param obj $field Object to evaluate - * @return bool TRUE if object is a valid field instance, FALSE otherwise - */ - function is_field(&$field) { - return is_a($field, 'cnr_field_type'); - } - - /*-** Handlers **-*/ - - /** - * Modifies query parameters to include custom content types - * Adds custom content types to default post query so these items are retrieved as well - * @param WP_Query $q Reference to WP_Query object being used to perform posts query - * @see WP_Query for reference - */ - function pre_get_posts($q) { - $pt =& $q->query_vars['post_type']; - /* Do not continue processing if: - * > In admin section - * > Not main query (or CNR-initiated query) - * > Single object requested - * > More than one post type is already specified - * > Post type other than 'post' is supplied - */ - if ( is_admin() - || ( !$q->is_main_query() && !isset($q->query_vars[$this->get_prefix()]) ) - || $q->is_singular() - || ( is_array($pt) - && ( count($pt) > 1 - || 'post' != $pt[0] ) - ) - || !in_array($pt, array('post', null)) - ) { - return false; - } - - $default_types = $this->get_default_post_types(); - $custom_types = array_diff(array_keys($this->get_types()), $default_types); - if ( !count($custom_types) ) - return false; - //Wrap post type in array - if ( empty($pt) || is_null($pt) ) - $pt = array('post'); - if ( !is_array($pt) ) - $pt = array($pt); - //Add custom types to query - foreach ( $custom_types as $type ) { - $pt[] = $type; - } - } - - /** - * Retrieves current context (content type, action) - * @return array Content Type and Action of current request - */ - function get_context() { - $post = false; - if ( isset($GLOBALS['post']) && !is_null($GLOBALS['post']) ) - $post = $GLOBALS['post']; - elseif ( isset($_REQUEST['post_id']) ) - $post = $_REQUEST['post_id']; - elseif ( isset($_REQUEST['post']) ) - $post = $_REQUEST['post']; - elseif ( isset($_REQUEST['post_type']) ) - $post = $_REQUEST['post_type']; - //Get action - $action = $this->util->get_action(); - if ( empty($post) ) - $post = $this->get_page_type(); - //Get post's content type - $ct =& $this->get_type($post); - - return array(&$ct, $action); - } - - /** - * Enqueues files for fields in current content type - * @param string $page Current context - */ - function enqueue_files($page = null) { - list($ct, $action) = $this->get_context(); - $file_types = array('scripts' => 'script', 'styles' => 'style'); - //Get content type fields - foreach ( $ct->fields as $field ) { - //Enqueue scripts/styles for each field - foreach ( $file_types as $type => $func_base ) { - $deps = $field->{"get_$type"}(); - foreach ( $deps as $handle => $args ) { - //Confirm context - if ( in_array('all', $args['context']) || in_array($page, $args['context']) || in_array($action, $args['context']) ) { - $this->enqueue_file($func_base, $args['params']); - } - } - } - } - } - - /** - * Add plugin hooks for fields used in current request - */ - function add_hooks() { - list($ct, $action) = $this->get_context(); - //Iterate through content type fields and add hooks from fields - foreach ( $ct->fields as $field ) { - //Iterate through hooks added to field - $hooks = $field->get_hooks(); - foreach ( $hooks as $tag => $callback ) { - //Iterate through function callbacks added to tag - foreach ( $callback as $id => $args ) { - //Check if hook/function was already processed - if ( isset($this->hooks_processed[$tag][$id]) ) - continue; - //Add hook/function to list of processed hooks - if ( !isset($this->hooks_processed[$tag]) || !is_array($this->hooks_processed[$tag]) ) - $this->hooks_processed[$tag] = array($id => true); - //Add hook to WP - call_user_func_array('add_filter', $args); - } - } - } - } - - /** - * Enqueues files - * @param string $type Type of file to enqueue (script or style) - * @param array $args (optional) Arguments to pass to enqueue function - */ - function enqueue_file($type = 'script', $args = array()) { - $func = 'wp_enqueue_' . $type; - if ( function_exists($func) ) { - call_user_func_array($func, $args); - } - } - - /** - * Add admin menus for content types - * @deprecated Not needed for 3.0+ - */ - function admin_menu() { - global $cnr_content_types; - - $pos = 21; - foreach ( $cnr_content_types as $id => $type ) { - if ( $this->is_default_post_type($id) ) - continue; - $page = $this->get_admin_page_file($id); - $callback = $this->m('admin_page'); - $access = 8; - $pos += 1; - $title = $type->get_title(true); - if ( !empty($title) ) { - //Main menu - add_menu_page($type->get_title(true), $type->get_title(true), $access, $page, $callback, '', $pos); - //Edit - add_submenu_page($page, __('Edit ' . $type->get_title(true)), __('Edit'), $access, $page, $callback); - $hook = get_plugin_page_hookname($page, $page); - add_action('load-' . $hook, $this->m('admin_menu_load_plugin')); - //Add - $page_add = $this->get_admin_page_file($id, 'add'); - add_submenu_page($page, __('Add New ' . $type->get_title()), __('Add New'), $access, $page_add, $callback); - $hook = get_plugin_page_hook($page_add, $page); - add_action('load-' . $hook, $this->m('admin_menu_load_plugin')); - //Hook for additional menus - $menu_hook = 'cnr_admin_menu_type'; - //Type specific - do_action_ref_array($menu_hook . '_' . $id, array(&$type)); - //General - do_action_ref_array($menu_hook, array(&$type)); - } - } - } - - /** - * Load data for plugin admin page prior to admin-header.php is loaded - * Useful for enqueueing scripts/styles, etc. - */ - function admin_menu_load_plugin() { - //Get Action - global $editing, $post, $post_ID, $p; - $action = $this->util->get_action(); - if ( isset($_GET['delete_all']) ) - $action = 'delete_all'; - if ( isset($_GET['action']) && 'edit' == $_GET['action'] && ! isset($_GET['bulk_edit'])) - $action = 'manage'; - switch ( $action ) { - case 'delete_all' : - case 'edit' : - //Handle bulk actions - //Redirect to edit.php for processing - - //Build query string - $qs = $_GET; - unset($qs['page']); - $edit_uri = admin_url('edit.php') . '?' . build_query($qs); - wp_redirect($edit_uri); - break; - case 'edit-item' : - wp_enqueue_script('admin_comments'); - enqueue_comment_hotkeys_js(); - //Get post being edited - if ( empty($_GET['post']) ) { - wp_redirect("post.php"); //TODO redirect to appropriate manage page - exit(); - } - $post_ID = $p = (int) $_GET['post']; - $post = get_post($post_ID); - if ( !current_user_can('edit_post', $post_ID) ) - wp_die( __('You are not allowed to edit this item') ); - - if ( $last = wp_check_post_lock($post->ID) ) { - add_action('admin_notices', '_admin_notice_post_locked'); - } else { - wp_set_post_lock($post->ID); - $locked = true; - } - //Continue on to add case - case 'add' : - $editing = true; - wp_enqueue_script('autosave'); - wp_enqueue_script('post'); - if ( user_can_richedit() ) - wp_enqueue_script('editor'); - add_thickbox(); - wp_enqueue_script('media-upload'); - wp_enqueue_script('word-count'); - add_action( 'admin_print_footer_scripts', 'wp_tiny_mce', 25 ); - wp_enqueue_script('quicktags'); - wp_enqueue_script($this->add_prefix('edit_form'), $this->util->get_file_url('js/lib.admin.edit_form.js'), array('jquery', 'postbox'), false, true); - break; - default : - wp_enqueue_script( $this->add_prefix('inline-edit-post') ); - } - } - - /** - * Build admin page file name for the specified post type - * @param string|CNR_Content_Type $type Content type ID or object - * @param string $action Action to build file name for - * @param bool $sep_action Whether action should be a separate query variable (Default: false) - * @return string Admin page file name - */ - function get_admin_page_file($type, $action = '', $sep_action = false) { - if ( isset($type->id) ) - $type = $type->id; - $page = $this->add_prefix('post_type_' . $type); - if ( !empty($action) ) { - if ( $sep_action ) - $page .= '&action='; - else - $page .= '-'; - - $page .= $action; - } - return $page; - } - - /** - * Determine content type based on URL query variables - * Uses $_GET['page'] variable to determine content type - * @return string Content type of page (NULL if no type defined by page) - */ - function get_page_type() { - $type = null; - //Extract type from query variable - if ( isset($_GET['page']) ) { - $type = $_GET['page']; - $prefix = $this->add_prefix('post_type_'); - //Remove plugin page prefix - if ( ($pos = strpos($type, $prefix)) === 0 ) - $type = substr($type, strlen($prefix)); - //Remove action (if present) - if ( ($pos = strrpos($type, '-')) && $pos !== false ) - $type = substr($type, 0, $pos); - } - return $type; - } - - /** - * Populate administration page for content type - */ - function admin_page() { - $prefix = $this->add_prefix('post_type_'); - if ( strpos($_GET['page'], $prefix) !== 0 ) - return false; - - //Get action - $action = $this->util->get_action('manage'); - //Get content type - $type =& $this->get_type($this->get_page_type()); - global $title, $parent_file, $submenu_file; - $title = $type->get_title(true); - //$parent_file = $prefix . $type->id; - //$submenu_file = $parent_file; - - switch ( $action ) { - case 'edit-item' : - case 'add' : - $this->admin_page_edit($type, $action); - break; - default : - $this->admin_page_manage($type, $action); - } - } - - /** - * Queries content items for admin management pages - * Also retrieves available post status for specified content type - * @see wp_edit_posts_query - * @param CNR_Content_Type|string $type Content type instance or ID - * @return array All item statuses and Available item status - */ - function admin_manage_query($type = 'post') { - global $wp_query; - $q = array(); - //Get post type - if ( ! is_a($type, 'CNR_Content_Type') ) { - $type = $this->get_type($type); - } - $q = array('post_type' => $type->id); - $g = $_GET; - //Date - $q['m'] = isset($g['m']) ? (int) $g['m'] : 0; - //Category - $q['cat'] = isset($g['cat']) ? (int) $g['cat'] : 0; - $post_stati = array( // array( adj, noun ) - 'publish' => array(_x('Published', 'post'), __('Published posts'), _n_noop('Published (%s)', 'Published (%s)')), - 'future' => array(_x('Scheduled', 'post'), __('Scheduled posts'), _n_noop('Scheduled (%s)', 'Scheduled (%s)')), - 'pending' => array(_x('Pending Review', 'post'), __('Pending posts'), _n_noop('Pending Review (%s)', 'Pending Review (%s)')), - 'draft' => array(_x('Draft', 'post'), _x('Drafts', 'manage posts header'), _n_noop('Draft (%s)', 'Drafts (%s)')), - 'private' => array(_x('Private', 'post'), __('Private posts'), _n_noop('Private (%s)', 'Private (%s)')), - 'trash' => array(_x('Trash', 'post'), __('Trash posts'), _n_noop('Trash (%s)', 'Trash (%s)')), - ); - - $post_stati = apply_filters('post_stati', $post_stati); - - $avail_post_stati = get_available_post_statuses('post'); - - //Status - if ( isset($g['post_status']) && in_array( $g['post_status'], array_keys($post_stati) ) ) { - $q['post_status'] = $g['post_status']; - $q['perm'] = 'readable'; - } else { - unset($q['post_status']); - } - - //Order - if ( isset($q['post_status']) && 'pending' === $q['post_status'] ) { - $q['order'] = 'ASC'; - $q['orderby'] = 'modified'; - } elseif ( isset($q['post_status']) && 'draft' === $q['post_status'] ) { - $q['order'] = 'DESC'; - $q['orderby'] = 'modified'; - } else { - $q['order'] = 'DESC'; - $q['orderby'] = 'date'; - } - - //Pagination - $posts_per_page = (int) get_user_option( 'edit_per_page', 0, false ); - if ( empty( $posts_per_page ) || $posts_per_page < 1 ) - $posts_per_page = 15; - if ( isset($g['paged']) && (int) $g['paged'] > 1 ) - $q['paged'] = (int) $g['paged']; - $q['posts_per_page'] = apply_filters( 'edit_posts_per_page', $posts_per_page ); - //Search - $q[s] = ( isset($g['s']) ) ? $g[s] : ''; - $wp_query->query($q); - - return array($post_stati, $avail_post_stati); - } - - /** - * Counts the number of items in the specified content type - * @see wp_count_posts - * @param CNR_Content_Type|string $type Content Type instance or ID - * @param string $perm Permission level for items (e.g. readable) - * @return array Associative array of item counts by post status (published, draft, etc.) - */ - function count_posts( $type, $perm = '' ) { - global $wpdb; - - $user = wp_get_current_user(); - - if ( !is_a($type, 'CNR_Content_Type') ) - $type = $this->get_type($type); - $type_val = $type->get_meta_value(); - $type = $type->id; - $cache_key = $type; - - //$query = "SELECT post_status, COUNT( * ) AS num_posts FROM {$wpdb->posts} WHERE post_type = %s"; - $query = "SELECT p.post_status, COUNT( * ) as num_posts FROM {$wpdb->posts} p JOIN {$wpdb->postmeta} m ON m.post_id = p.id WHERE m.meta_key = '" . $this->get_type_meta_key() . "' AND m.meta_value = '$type_val'"; - if ( 'readable' == $perm && is_user_logged_in() ) { - //TODO enable check for custom post types "read_private_{$type}s" - if ( !current_user_can("read_private_posts") ) { - $cache_key .= '_' . $perm . '_' . $user->ID; - $query .= " AND (p.post_status != 'private' OR ( p.post_author = '$user->ID' AND p.post_status = 'private' ))"; - } - } - $query .= ' GROUP BY p.post_status'; - - $count = wp_cache_get($cache_key, 'counts'); - if ( false !== $count ) - return $count; - - $count = $wpdb->get_results( $wpdb->prepare( $query, $type ), ARRAY_A ); - - $stats = array( 'publish' => 0, 'private' => 0, 'draft' => 0, 'pending' => 0, 'future' => 0, 'trash' => 0 ); - foreach( (array) $count as $row_num => $row ) { - $stats[$row['post_status']] = $row['num_posts']; - } - - $stats = (object) $stats; - wp_cache_set($cache_key, $stats, 'counts'); - - return $stats; - } - - /** - * Builds management page for items of a specific custom content type - * @param CNR_Content_Type $type Content Type to manage - * @param string $action Current action - * - * @global string $title - * @global string $parent_file - * @global string $plugin_page - * @global string $page_hook - * @global WP_User $current_user - * @global WP_Query $wp_query - * @global wpdb $wpdb - * @global WP_Locale $wp_locale - */ - function admin_page_manage($type, $action) { - if ( !current_user_can('edit_posts') ) - wp_die(__('You do not have sufficient permissions to access this page.')); - - global $title, $parent_file, $plugin_page, $page_hook, $current_user, $wp_query, $wpdb, $wp_locale; - $title = __('Edit ' . $type->get_title(true)); - $admin_path = ABSPATH . 'wp-admin/'; - - //Pagination - if ( ! isset($_GET['paged']) ) - $_GET['paged'] = 1; - - $add_url = $this->get_admin_page_url($type->id, 'add'); - $is_trash = isset($_GET['post_status']) && $_GET['post_status'] == 'trash'; - //User posts - $user_posts = false; - if ( !current_user_can('edit_others_posts') ) { - $user_posts_count = $wpdb->get_var( $wpdb->prepare("SELECT COUNT(1) FROM $wpdb->posts p JOIN $wpdb->postmeta m ON m.post_id = p.id WHERE m.meta_key = '_cnr_post_type' AND m.meta_value = %s AND p.post_status != 'trash' AND p.post_author = %d", $type->get_meta_value(), $current_user->ID) ); - $user_posts = true; - if ( $user_posts_count && empty($_GET['post_status']) && empty($_GET['all_posts']) && empty($_GET['author']) ) - $_GET['author'] = $current_user->ID; - } - //Get content type items - list($post_stati, $avail_post_stati) = $this->admin_manage_query($type->id); - ?> -
- -

' . __('Search results for “%s”') . '', esc_html( get_search_query() ) ); ?> -

- -
- - - - - - -
- add_query_arg( 'paged', '%#%' ), - 'format' => '', - 'prev_text' => __('«'), - 'next_text' => __('»'), - 'total' => $wp_query->max_num_pages, - 'current' => $_GET['paged'] - )); - ?> -
- - - - - posts p JOIN $wpdb->postmeta m ON m.post_id = p.ID WHERE m.meta_key = '" . $this->get_type_meta_key() . "' AND m.meta_value = '" . $type->get_meta_value() . "' ORDER BY post_date DESC"; - - $arc_result = $wpdb->get_results( $arc_query ); - - $month_count = count($arc_result); - - if ( $month_count && !( 1 == $month_count && 0 == $arc_result[0]->mmonth ) ) { - $m = isset($_GET['m']) ? (int)$_GET['m'] : 0; - ?> - - __('View all categories'), 'hide_empty' => 0, 'hierarchical' => 1, - 'show_count' => 0, 'orderby' => 'name', 'selected' => $cat); - wp_dropdown_categories($dropdown_options); - do_action('restrict_manage_posts'); - ?> - - - - -
- - -
' . __( 'Displaying %s–%s of %s' ) . '%s', - number_format_i18n( ( $_GET['paged'] - 1 ) * $wp_query->query_vars['posts_per_page'] + 1 ), - number_format_i18n( min( $_GET['paged'] * $wp_query->query_vars['posts_per_page'], $wp_query->found_posts ) ), - number_format_i18n( $wp_query->found_posts ), - $page_links - ); echo $page_links_text; ?>
- -
-
- -
-

- -
- -
-
-
- get_title()); - $admin_path = ABSPATH . 'wp-admin/'; - include ($admin_path . 'edit-form-advanced.php'); - } - - /** - * Adds hidden field declaring content type on post edit form - * @deprecated no longer needed for WP 3.0+ - */ - function admin_page_edit_form() { - global $post, $plugin_page; - if ( empty($post) || !$post->ID ) { - $type = $this->get_type($post); - if ( ! empty($type) && ! empty($type->id) ) { - ?> - - get_types()), array('post', 'page'))) ) { - //Get content type definition - $ct =& $this->get_type($post); - //Pass processing to content type instance - $ct->admin_do_meta_boxes($type, $context, $post); - } - } - - /** - * Saves field data submitted for current post - * @param int $post_id ID of current post - * @param object $post Post object - */ - function save_item_data($post_id, $post) { - if ( empty($post_id) || empty($post) || !isset($_POST['cnr']) || !is_array($_POST['cnr']) ) - return false; - $pdata = $_POST['cnr']; - - if ( isset($pdata['attributes']) && is_array($pdata['attributes']) && isset($pdata['fields_loaded']) && is_array($pdata['fields_loaded']) ) { - - $prev_data = (array) $this->get_item_data($post_id); - - //Remove loaded fields from prev data - $prev_data = array_diff_key($prev_data, $pdata['fields_loaded']); - - //Get current field data - $curr_data = $pdata['attributes']; - - //Merge arrays together (new data overwrites old data) - if ( is_array($prev_data) && is_array($curr_data) ) { - $curr_data = array_merge($prev_data, $curr_data); - } - - //Save to database - update_post_meta($post_id, $this->get_fields_meta_key(), $curr_data); - } - //Save content type - if ( isset($_POST['cnr']['content_type']) ) { - $type = $_POST['cnr']['content_type']; - $saved_type = get_post_meta($post_id, $this->get_type_meta_key(), true); - if ( is_array($saved_type) ) - $saved_type = implode($saved_type); - if ( $type != $saved_type ) { - //Continue processing if submitted content type is different from previously-saved content type (or no type was previously set) - update_post_meta($post_id, $this->get_type_meta_key(), array($type)); - } - } - } - - /*-** Helpers **-*/ - - /** - * Get array of default post types - * @return array Default post types - */ - function get_default_post_types() { - return array('post', 'page', 'attachment', 'revision', 'nav_menu'); - } - - /** - * Checks if post's post type is a standard WP post type - * @param mixed $post_type Post type (default) or post ID/object to evaluate - * @see CNR_Content_Utilities::get_type() for possible parameter values - * @return bool TRUE if post is default type, FALSE if it is a custom type - */ - function is_default_post_type($post_type) { - if ( !is_string($post_type) ) { - $post_type = $this->get_type($post_type); - $post_type = $post_type->id; - } - return in_array($post_type, $this->get_default_post_types()); - } - - /** - * Checks if specified content type has been defined - * @param string|CNR_Content_Type $type Content type ID or object - * @return bool TRUE if content type exists, FALSE otherwise - * - * @uses array $cnr_content_types - */ - function type_exists($type) { - global $cnr_content_types; - if ( ! is_scalar($type) ) { - if ( is_a($type, 'CNR_Content_Type') ) - $type = $type->id; - else - $type = null; - } - return ( isset($cnr_content_types[$type]) ); - } - - /** - * Retrieves content type definition for specified content item (post, page, etc.) - * If content type does not exist, a new instance object will be created and returned - * > New content types are automatically registered (since we are looking for registered types when using this method) - * @param string|object $item Post object, or item type (string) - * @return CNR_Content_Type Reference to matching content type, empty content type if no matching type exists - * - * @uses array $cnr_content_types - */ - function &get_type($item) { - //Return immediately if $item is a content type instance - if ( is_a($item, 'CNR_Content_Type') ) - return $item; - - $type = null; - - if ( is_string($item) ) - $type = $item; - - if ( !$this->type_exists($type) ) { - $post = $item; - - //Check if $item is a post (object or ID) - if ( $this->util->check_post($post) && isset($post->post_type) ) { - $type = $post->post_type; - } - } - global $cnr_content_types; - if ( $this->type_exists($type) ) { - //Retrieve content type from global array - $type =& $cnr_content_types[$type]; - } else { - //Create new empty content type if it does not already exist - $type = new CNR_Content_Type($type); - //Automatically register newly initialized content type if it extends an existing WP post type - if ( $this->is_default_post_type($type->id) ) - $type->register(); - } - - return $type; - } - - /** - * Retrieve content types - * @return Reference to content types array - */ - function &get_types() { - return $GLOBALS['cnr_content_types']; - } - - /** - * Retrieve meta key for post fields - * @return string Fields meta key - */ - function get_fields_meta_key() { - return $this->util->make_meta_key('fields'); - } - - /** - * Retrieve meta key for post type - * @return string Post type meta key - */ - function get_type_meta_key() { - return $this->util->make_meta_key('post_type'); - } - - /** - * Checks if post contains specified field data - * @param Object $post (optional) Post to check data for - * @param string $field (optional) Field ID to check for - * @return bool TRUE if data exists, FALSE otherwise - */ - function has_item_data($item = null, $field = null) { - $ret = $this->get_item_data($item, $field, 'raw', null); - if ( is_scalar($ret) ) - return ( !empty($ret) || $ret === 0 ); - if ( is_array($ret) ) { - foreach ( $ret as $key => $val ) { - if ( !empty($val) || $val === 0 ) - return true; - } - } - return false; - } - - /** - * Retrieve specified field data from content item (e.g. post) - * Usage Examples: - * get_item_data($post_id, 'field_id') - * - Retrieves field_id data from global $post object - * - Field data is formatted using 'display' layout of field - * - * get_item_data($post_id, 'field_id', 'raw') - * - Retrieves field_id data from global $post object - * - Raw field data is returned (no formatting) - * - * get_item_data($post_id, 'field_id', 'display', $post_id) - * - Retrieves field_id data from post matching $post_id - * - Field data is formatted using 'display' layout of field - * - * get_item_data($post_id, 'field_id', null) - * - Retrieves field_id data from post matching $post_id - * - Field data is formatted using 'display' layout of field - * - The default layout is used when no valid layout is specified - * - * get_item_data($post_id) - * - Retrieves full data array from post matching $post_id - * - * @param int|object $item(optional) Content item to retrieve field from (Default: null - global $post object will be used) - * @param string $field ID of field to retrieve - * @param string $layout(optional) Layout to use when returning field data (Default: display) - * @param array $attr (optional) Additional attributes to pass along to field object (e.g. for building layout, etc.) - * @see CNR_Field_Type::build_layout for more information on attribute usage - * @return mixed Specified field data - */ - function get_item_data($item = null, $field = null, $layout = null, $default = '', $attr = null) { - $ret = $default; - - //Get item - $item = get_post($item); - - if ( !isset($item->ID) ) - return $ret; - - //Get item data - $data = get_post_meta($item->ID, $this->get_fields_meta_key(), true); - - //Get field data - - //Set return value to data if no field specified - if ( empty($field) || !is_string($field) ) - $ret = $data; - //Stop if no valid field specified - if ( !isset($data[$field]) ) { - //TODO Check $item object to see if specified field exists (e.g. title, post_status, etc.) - return $ret; - } - - $ret = $data[$field]; - - //Initialize layout value - $layout_def = 'display'; - - if ( !is_scalar($layout) || empty($layout) ) - $layout = $layout_def; - - $layout = strtolower($layout); - - //Check if raw data requested - if ( 'raw' == $layout ) - return $ret; - - /* Build specified layout */ - - //Get item's content type - $ct =& $this->get_type($item); - $ct->set_data($data); - - //Get field definition - $fdef =& $ct->get_field($field); - - //Validate layout - if ( !$fdef->has_layout($layout) ) - $layout = $layout_def; - - //Build layout - $fdef->set_caller($ct); - $ret = $fdef->build_layout($layout, $attr); - $fdef->clear_caller(); - - //Return formatted value - return $ret; - } - - /** - * Prints an item's field data - * @see CNR_Content_Utilities::get_item_data() for more information - * @param int|object $item(optional) Content item to retrieve field from (Default: null - global $post object will be used) - * @param string $field ID of field to retrieve - * @param string $layout(optional) Layout to use when returning field data (Default: display) - * @param mixed $default (optional) Default value to return in case of errors, etc. - * @param array $attr Additional attributes to pass to field - */ - function the_item_data($item = null, $field = null, $layout = null, $default = '', $attr = null) { - echo apply_filters('cnr_the_item_data', $this->get_item_data($item, $field, $layout, $default, $attr), $item, $field, $layout, $default, $attr); - } - - /** - * Build Admin URL for specified post type - * @param string|CNR_Content_Type $type Content type ID or object - * @param string $action Action to build URL for - * @param bool $sep_action Whether action should be a separate query variable (Default: false) - * @return string Admin page URL - */ - function get_admin_page_url($type, $action = '', $sep_action = false) { - $url = admin_url('admin.php'); - $url .= '?page=' . $this->get_admin_page_file($type, $action, $sep_action); - return $url; - } - - function get_edit_item_url($edit_url, $item_id, $context) { - //Get post type - $type = $this->get_type($item_id); - if ( ! $this->is_default_post_type($type->id) && $this->type_exists($type) ) { - $edit_url = $this->get_admin_page_url($type, 'edit-item', true) . '&post=' . $item_id; - } - - return $edit_url; - } -} ?> diff --git a/includes/class.content_base.php b/includes/class.content_base.php new file mode 100644 index 0000000..f64165d --- /dev/null +++ b/includes/class.content_base.php @@ -0,0 +1,613 @@ +id = $id; + if ( is_bool($parent) && $parent ) + $parent = $id; + $this->set_parent($parent); + } + + /* Getters/Setters */ + + /** + * Checks if the specified path exists in the object + * @param array $path Path to check for + * @return bool TRUE if path exists in object, FALSE otherwise + */ + function path_isset($path = '') { + //Stop execution if no path is supplied + if ( empty($path) ) + return false; + $args = func_get_args(); + $path = $this->util->build_path($args); + $item =& $this; + //Iterate over path and check if each level exists before moving on to the next + for ($x = 0; $x < count($path); $x++) { + if ( $this->util->property_exists($item, $path[$x]) ) { + //Set $item as reference to next level in path for next iteration + $item =& $this->util->get_property($item, $path[$x]); + //$item =& $item[ $path[$x] ]; + } else { + return false; + } + } + return true; + } + + /** + * Retrieves a value from object using a specified path + * Checks to make sure path exists in object before retrieving value + * @param array $path Path to retrieve value from. Each item in array is a deeper dimension + * @return mixed Value at specified path + */ + function &get_path_value($path = '') { + $ret = ''; + $path = $this->util->build_path(func_get_args()); + if ( $this->path_isset($path) ) { + $ret =& $this; + for ($x = 0; $x < count($path); $x++) { + if ( 0 == $x ) + $ret =& $ret->{ $path[$x] }; + else + $ret =& $ret[ $path[$x] ]; + } + } + return $ret; + } + + /** + * Search for specified member value in field type ancestors + * @param string $member Name of object member to search (e.g. properties, layout, etc.) + * @param string $name Value to retrieve from member + * @return mixed Member value if found (Default: empty string) + */ + function get_parent_value($member, $name = '', $default = '') { + $parent =& $this->get_parent(); + return $this->get_object_value($parent, $member, $name, $default, 'parent'); + } + + /** + * Retrieves specified member value + * Handles inherited values + * Merging corresponding parents if value is an array (e.g. for property groups) + * @param string|array $member Member to search. May also contain a path to the desired member + * @param string $name Value to retrieve from member + * @param mixed $default Default value if no value found (Default: empty string) + * @param string $dir Direction to move through hierarchy to find value + * Possible Values: + * parent (default) - Search through field parents + * current - Do not search through connected objects + * container - Search through field containers + * caller - Search through field callers + * @return mixed Specified member value + */ + function get_member_value($member, $name = '', $default = '', $dir = 'parent') { + //Check if path to member is supplied + $path = array(); + if ( is_array($member) && isset($member['tag']) ) { + if ( isset($member['attributes']['ref_base']) ) { + if ( 'root' != $member['attributes']['ref_base'] ) + $path[] = $member['attributes']['ref_base']; + } else { + $path[] = 'properties'; + } + + $path[] = $member['tag']; + } else { + $path = $member; + } + + $path = $this->util->build_path($path, $name); + //Set defaults and prepare data + $val = $default; + $inherit = false; + $inherit_tag = '{inherit}'; + + /* Determine whether the value must be retrieved from a parent/container object + * Conditions: + * > Path does not exist in current field + * > Path exists and is not an object, but at least one of the following is true: + * > Value at path is an array (e.g. properties, elements, etc. array) + * > Parent/container values should be merged with retrieved array + * > Value at path is a string that inherits from another field + * > Value from other field will be retrieved and will replace inheritance placeholder in retrieved value + */ + + $deeper = false; + + if ( !$this->path_isset($path) ) + $deeper = true; + else { + $val = $this->get_path_value($path); + if ( !is_object($val) && ( is_array($val) || ($inherit = strpos($val, $inherit_tag)) !== false ) ) + $deeper = true; + else + $deeper = false; + } + if ( $deeper && 'current' != $dir ) { + //Get Parent value (recursive) + $ex_val = ( 'parent' != $dir ) ? $this->get_container_value($member, $name, $default) : $this->get_parent_value($member, $name, $default); + //Handle inheritance + if ( is_array($val) ) { + //Combine Arrays + if ( is_array($ex_val) ) + $val = array_merge($ex_val, $val); + } elseif ( $inherit !== false ) { + //Replace placeholder with inherited string + $val = str_replace($inherit_tag, $ex_val, $val); + } else { + //Default: Set parent value as value + $val = $ex_val; + } + } + + return $val; + } + + /** + * Search for specified member value in an object + * @param object $object Reference to object to retrieve value from + * @param string $member Name of object member to search (e.g. properties, layout, etc.) + * @param string $name (optional) Value to retrieve from member + * @param mixed $default (optional) Default value to use if no value found (Default: empty string) + * @param string $dir Direction to move through hierarchy to find value @see CNR_Field_Type::get_member_value() for possible values + * @return mixed Member value if found (Default: $default) + */ + function get_object_value(&$object, $member, $name = '', $default = '', $dir = 'parent') { + $ret = $default; + if ( is_object($object) && method_exists($object, 'get_member_value') ) + $ret = $object->get_member_value($member, $name, $default, $dir); + return $ret; + } + + /** + * Retrieve value from data member + * @param bool $top (optional) Whether to traverse through the field hierarchy to get data for field (Default: TRUE) + * @return mixed Value at specified path + */ + function get_data($top = true) { + $top = !!$top; + $obj = $this; + $obj_path = array($this); + $path = array(); + //Iterate through hiearchy to get top-most object + while ( !empty($obj) ) { + $new = null; + //Try to get caller first + if ( method_exists($obj, 'get_caller') ) { + $checked = true; + $new = $obj->get_caller(); + } + //Try to get container if no caller found + if ( empty($new) && method_exists($obj, 'get_container') ) { + $checked = true; + $new = $obj->get_container(); + } + + $obj = $new; + + //Stop iteration + if ( !empty($obj) ) { + //Add object to path if it is valid + $obj_path[] = $obj; + } + } + + //Check each object (starting with top-most) for matching data for current field + + //Reverse array + $obj_path = array_reverse($obj_path); + //Build path for data location + foreach ( $obj_path as $obj ) { + if ( $this->util->property_exists($obj, 'id') ) + $path[] = $obj->id; + } + + //Iterate through objects + while ( !empty($obj_path) ) { + //Get next object + $obj = array_shift($obj_path); + //Shorten path + array_shift($path); + //Check for value in object and stop iteration if matching data found + if ( ($val = $this->get_object_value($obj, 'data', $path, null, 'current')) && !is_null($val) ) { + break; + } + } + + return $val; + } + + /** + * Sets value in data member + * Sets value to data member itself by default + * @param mixed $value Value to set + * @param string|array $name Name of value to set (Can also be path to value) + */ + function set_data($value, $name = '') { + $ref =& $this->get_path_value('data', $name); + $ref = $value; + } + + /** + * Retrieve base_class property + * @return string base_class property of current class/instance object + */ + function get_base_class() { + $ret = ''; + if ( isset($this) ) + $ret = $this->base_class; + else { + $ret = CNR_Utilities::get_property(__CLASS__, 'base_class'); + } + + return $ret; + } + + /** + * Sets parent object of current instance + * Parent objects must be the same object type as current instance + * @param string|object $parent Parent ID or reference + */ + function set_parent($parent) { + if ( !empty($parent) ) { + //Validate parent object + if ( is_array($parent) ) + $parent =& $parent[0]; + + //Retrieve reference object if ID was supplied + if ( is_string($parent) ) { + $parent = trim($parent); + //Check for existence of parent + $lookup = $this->base_class . 's'; + if ( isset($GLOBALS[$lookup][$parent]) ) { + //Get reference to parent + $parent =& $GLOBALS[$lookup][$parent]; + } + } + + //Set reference to parent field type + if ( is_a($parent, $this->base_class) ) { + $this->parent =& $parent; + } + } + } + + /** + * Retrieve field type parent + * @return CNR_Field_Type Reference to parent field + */ + function &get_parent() { + return $this->parent; + } + + /** + * Retrieves field ID + * @param string|CNR_Field|array $field (optional) Field object or ID of field or options array + * @return string|bool Field ID, FALSE if $field is invalid + */ + function get_id($field = null) { + $ret = false; + if ( ( !is_object($field) || !is_a($field, 'cnr_field_type') ) && isset($this) ) { + $field =& $this; + } + + if ( is_a($field, CNR_Field_Type::get_base_class()) ) + $id = $field->id; + + if ( is_string($id) ) + $ret = trim($id); + + //Setup options + $options_def = array('format' => null); + //Get options array + $num_args = func_num_args(); + $options = ( $num_args > 0 && ( $last_arg = func_get_arg($num_args - 1) ) && is_array($last_arg) ) ? $last_arg : array(); + $options = wp_parse_args($options, $options_def); + //Check if field should be formatted + if ( is_string($ret) && !empty($options['format']) ) { + //Clear format option if it is an invalid value + if ( is_bool($options['format']) || is_int($options['format']) ) + $options['format'] = null; + //Setup values + $wrap = array('open' => '[', 'close' => ']'); + if ( isset($options['wrap']) && is_array($options['wrap']) ) + $wrap = wp_parse_args($options['wrap'], $wrap); + $wrap_trailing = ( isset($options['wrap_trailing']) ) ? !!$options['wrap_trailing'] : true; + switch ( $options['format'] ) { + case 'attr_id' : + $wrap = (array('open' => '_', 'close' => '_')); + $wrap_trailing = false; + break; + } + $c = $field->get_caller(); + $field_id = array($ret); + while ( !!$c ) { + //Add ID of current field to array + if ( isset($c->id) && is_a($c, $this->base_class) ) + $field_id[] = $c->id; + $c = ( method_exists($c, 'get_caller') ) ? $c->get_caller() : null; + } + + //Add prefix to ID value + $field_id[] = 'attributes'; + + //Convert array to string + return $field->prefix . $wrap['open'] . implode($wrap['close'] . $wrap['open'], array_reverse($field_id)) . ( $wrap_trailing ? $wrap['close'] : ''); + } + return $ret; + } + + /** + * Set object title + * @param string $title Title for object + * @param string $plural Plural form of title + */ + function set_title($title = '', $plural = '') { + $this->title = strip_tags(trim($title)); + if ( isset($plural) ) + $this->title_plural = strip_tags(trim($plural)); + } + + /** + * Retrieve object title + * @param bool $plural TRUE if plural title should be retrieved, FALSE otherwise (Default: FALSE) + */ + function get_title($plural = false) { + $dir = 'current'; + //Singular + if ( !$plural ) + return $this->get_member_value('title', '','', $dir); + //Plural + $title = $this->get_member_value('title_plural', '', '', $dir); + if ( empty($title) ) { + //Use singular title for plural base + $title = $this->get_member_value('title', '', '', $dir); + //Determine technique for making title plural + //Get last letter + if ( !empty($title) ) { + $tail = substr($title, -1); + switch ( $tail ) { + case 's' : + $title .= 'es'; + break; + case 'y' : + $title = substr($title, 0, -1) . 'ies'; + break; + default : + $title .= 's'; + } + } + } + return $title; + } + + /** + * Set object description + * @param string $description Description for object + */ + function set_description($description = '') { + $this->description = strip_tags(trim($description)); + } + + /** + * Retrieve object description + * @return string Object description + */ + function get_description() { + $dir = 'current'; + return $this->get_member_value('description', '','', $dir); + return $desc; + } + + /*-** Hooks **-*/ + + /** + * Retrieve hooks added to object + * @return array Hooks + */ + function get_hooks() { + return $this->get_member_value('hooks', '', array()); + } + + /** + * Add hook for object + * @see add_filter() for parameter defaults + * @param $tag + * @param $function_to_add + * @param $priority + * @param $accepted_args + */ + function add_hook($tag, $function_to_add, $priority = 10, $accepted_args = 1) { + //Create new array for tag (if not already set) + if ( !isset($this->hooks[$tag]) ) + $this->hooks[$tag] = array(); + //Build Unique ID + if ( is_string($function_to_add) ) + $id = $function_to_add; + elseif ( is_array($function_to_add) && !empty($function_to_add) ) + $id = strval($function_to_add[count($function_to_add) - 1]); + else + $id = 'function_' . ( count($this->hooks[$tag]) + 1 ); + //Add hook + $this->hooks[$tag][$id] = func_get_args(); + } + + /** + * Convenience method for adding an action for object + * @see add_filter() for parameter defaults + * @param $tag + * @param $function_to_add + * @param $priority + * @param $accepted_args + */ + function add_action($tag, $function_to_add, $priority = 10, $accepted_args = 1) { + $this->add_hook($tag, $function_to_add, $priority, $accepted_args); + } + + /** + * Convenience method for adding a filter for object + * @see add_filter() for parameter defaults + * @param $tag + * @param $function_to_add + * @param $priority + * @param $accepted_args + */ + function add_filter($tag, $function_to_add, $priority = 10, $accepted_args = 1) { + $this->add_hook($tag, $function_to_add, $priority, $accepted_args); + } + + /*-** Dependencies **-*/ + + /** + * Adds dependency to object + * @param string $type Type of dependency to add (script, style) + * @param array|string $context When dependency will be added (@see CNR_Utilities::get_action() for possible contexts) + * @see wp_enqueue_script for the following of the parameters + * @param $handle + * @param $src + * @param $deps + * @param $ver + * @param $ex + */ + function add_dependency($type, $context, $handle, $src = false, $deps = array(), $ver = false, $ex = false) { + $args = func_get_args(); + //Remove type/context from arguments + $args = array_slice($args, 2); + + //Set context + if ( !is_array($context) ) { + //Wrap single contexts in an array + if ( is_string($context) ) + $context = array($context); + else + $context = array(); + } + //Add file to instance property + $this->{$type}[$handle] = array('context' => $context, 'params' => $args); + } + + /** + * Add script to object to be added in specified contexts + * @param array|string $context Array of contexts to add script to page + * @see wp_enqueue_script for the following of the parameters + * @param $handle + * @param $src + * @param $deps + * @param $ver + * @param $in_footer + */ + function add_script( $context, $handle, $src = false, $deps = array(), $ver = false, $in_footer = false ) { + $args = func_get_args(); + //Add file type to front of arguments array + array_unshift($args, 'scripts'); + call_user_func_array(array(&$this, 'add_dependency'), $args); + } + + /** + * Retrieve script dependencies for object + * @return array Script dependencies + */ + function get_scripts() { + return $this->get_member_value('scripts', '', array()); + } + + /** + * Add style to object to be added in specified contexts + * @param array|string $context Array of contexts to add style to page + * @see wp_enqueue_style for the following of the parameters + * @param $handle + * @param $src + * @param $deps + * @param $ver + * @param $in_footer + */ + function add_style( $handle, $src = false, $deps = array(), $ver = false, $media = false ) { + $args = func_get_args(); + array_unshift($args, 'styles'); + call_user_method_array('add_dependency', $this, $args); + } + + /** + * Retrieve Style dependencies for object + * @return array Style dependencies + */ + function get_styles() { + return $this->get_member_value('styles', '', array()); + } +} \ No newline at end of file diff --git a/includes/class.content_type.php b/includes/class.content_type.php new file mode 100644 index 0000000..65059d7 --- /dev/null +++ b/includes/class.content_type.php @@ -0,0 +1,397 @@ + description string Group description + * > location string Location of group on edit form + * > fields array Fields in group + * @var array + */ + var $groups = array(); + + /* Constructors */ + + /** + * Class constructor + * @param string $id Content type ID + * @param string|bool $parent (optional) Parent to inherit properties from (Default: none) + * @param array $properties (optional) Properties to set for content type (Default: none) + */ + function __construct($id = '', $parent = null, $properties = null) { + parent::__construct($id, $parent); + + //Set properties + //TODO Iterate through additional arguments and set instance properties + } + + /* Registration */ + + /** + * Registers current content type w/CNR + */ + function register() { + global $cnr_content_utilities; + $cnr_content_utilities->register_content_type($this); + } + + /* Getters/Setters */ + + /** + * Adds group to content type + * Groups are used to display related fields in the UI + * @param string $id Unique name for group + * @param string $title Group title + * @param string $description Short description of group's purpose + * @param string $location Where group will be displayed on post edit form (Default: main) + * @param array $fields (optional) ID's of existing fields to add to group + * @return object Group object + */ + function &add_group($id, $title = '', $description = '', $location = 'normal', $fields = array()) { + //Create new group and set properties + $id = trim($id); + $this->groups[$id] =& $this->create_group($title, $description, $location); + //Add fields to group (if supplied) + if ( !empty($fields) && is_array($fields) ) + $this->add_to_group($id, $fields); + return $this->groups[$id]; + } + + /** + * Remove specified group from content type + * @param string $id Group ID to remove + */ + function remove_group($id) { + $id = trim($id); + if ( $this->group_exists($id) ) { + unset($this->groups[$id]); + } + } + + /** + * Standardized method to create a new field group + * @param string $title Group title (used in meta boxes, etc.) + * @param string $description Short description of group's purpose + * @param string $location Where group will be displayed on post edit form (Default: main) + * @return object Group object + */ + function &create_group($title = '', $description = '', $location = 'normal') { + $group = new stdClass(); + $title = ( is_scalar($title) ) ? trim($title) : ''; + $group->title = $title; + $description = ( is_scalar($description) ) ? trim($description) : ''; + $group->description = $description; + $location = ( is_scalar($location) ) ? trim($location) : 'normal'; + $group->location = $location; + $group->fields = array(); + return $group; + } + + /** + * Checks if group exists + * @param string $id Group name + * @return bool TRUE if group exists, FALSE otherwise + */ + function group_exists($id) { + $id = trim($id); + //Check if group exists in content type + return ( !is_null($this->get_member_value('groups', $id, null)) ); + } + + /** + * Adds field to content type + * @param string $id Unique name for field + * @param CNR_Field_Type|string $parent Field type that this field is based on + * @param array $properties (optional) Field properties + * @param string $group (optional) Group ID to add field to + * @return CNR_Field Reference to new field + */ + function &add_field($id, $parent, $properties = array(), $group = null) { + //Create new field + $id = trim(strval($id)); + $field = new CNR_Field($id); + $field->set_parent($parent); + $field->set_container($this); + $field->set_properties($properties); + + //Add field to content type + $this->fields[$id] =& $field; + //Add field to group + $this->add_to_group($group, $field->id); + return $field; + } + + /** + * Removes field from content type + * @param string|CNR_Field $field Object or Field ID to remove + */ + function remove_field($field) { + $field = CNR_Field_Type::get_id($field); + if ( !$field ) + return false; + + //Remove from fields array + //$this->fields[$field] = null; + unset($this->fields[$field]); + + //Remove field from groups + $this->remove_from_group($field); + } + + /** + * Retrieve specified field in Content Type + * @param string $field Field ID + * @return CNR_Field Specified field + */ + function &get_field($field) { + if ( $this->has_field($field) ) { + $field = trim($field); + $field = $this->get_member_value('fields', $field); + } else { + //Return empty field if no field exists + $field = new CNR_Field(''); + } + return $field; + } + + /** + * Checks if field exists in the content type + * @param string $field Field ID + * @return bool TRUE if field exists, FALSE otherwise + */ + function has_field($field) { + return ( !is_string($field) || empty($field) || is_null($this->get_member_value('fields', $field, null)) ) ? false : true; + } + + /** + * Adds field to a group in the content type + * Group is created if it does not already exist + * @param string|array $group ID of group (or group parameters if new group) to add field to + * @param string|array $fields Name or array of field(s) to add to group + */ + function add_to_group($group, $fields) { + //Validate parameters + $group_id = ''; + if ( !empty($group) ) { + if ( !is_array($group) ) { + $group = array($group, $group); + } + + $group[0] = $group_id = trim(sanitize_title_with_dashes($group[0])); + } + if ( empty($group_id) || empty($fields) ) + return false; + //Create group if it doesn't exist + if ( !$this->group_exists($group_id) ) { + call_user_func_array($this->m('add_group'), $group); + } + if ( ! is_array($fields) ) + $fields = array($fields); + foreach ( $fields as $field ) { + unset($fref); + if ( ! $this->has_field($field) ) + continue; + $fref =& $this->get_field($field); + //Remove field from any other group it's in (fields can only be in one group) + foreach ( array_keys($this->groups) as $group_name ) { + if ( isset($this->groups[$group_name]->fields[$fref->id]) ) + unset($this->groups[$group_name]->fields[$fref->id]); + } + //Add reference to field in group + $this->groups[$group_id]->fields[$fref->id] =& $fref; + } + } + + /** + * Remove field from a group + * If no group is specified, then field is removed from all groups + * @param string|CNR_Field $field Field object or ID of field to remove from group + * @param string $group (optional) Group ID to remove field from + */ + function remove_from_group($field, $group = '') { + //Get ID of field to remove or stop execution if field invalid + $field = CNR_Field_Type::get_id($field); + if ( !$field ) + return false; + + //Remove field from group + if ( !empty($group) ) { + //Remove field from single group + if ( ($group =& $this->get_group($group)) && isset($group->fields[$field]) ) { + unset($group->fields[$field]); + } + } else { + //Remove field from all groups + foreach ( array_keys($this->groups) as $group ) { + if ( ($group =& $this->get_group($group)) && isset($group->fields[$field]) ) { + unset($group->fields[$field]); + } + } + } + } + + /** + * Retrieve specified group + * @param string $group ID of group to retrieve + * @return object Reference to specified group + */ + function &get_group($group) { + $group = trim($group); + //Create group if it doesn't already exist + if ( ! $this->group_exists($group) ) + $this->add_group($group); + $group = $this->get_member_value('groups', $group); + return $group; + } + + /** + * Retrieve all groups in content type + * @return array Reference to group objects + */ + function &get_groups() { + $groups = $this->get_member_value('groups'); + return $groups; + } + + /** + * Output fields in a group + * @param string $group ID of Group to output + * @return string Group output + */ + function build_group($group) { + $out = array(); + $classnames = (object) array( + 'multi' => 'multi_field', + 'single' => 'single_field', + 'elements' => 'has_elements' + ); + + //Stop execution if group does not exist + if ( $this->group_exists($group) && $group =& $this->get_group($group) ) { + $group_fields = ( count($group->fields) > 1 ) ? $classnames->multi : $classnames->single . ( ( ( $fs = array_keys($group->fields) ) && ( $f =& $group->fields[$fs[0]] ) && ( $els = $f->get_member_value('elements', '', null) ) && !empty($els) ) ? '_' . $classnames->elements : '' ); + $classname = array('cnr_attributes_wrap', $group_fields); + $out[] = '
'; //Wrap all fields in group + + //Build layout for each field in group + foreach ( array_keys($group->fields) as $field_id ) { + /** + * CNR_Field_Type + */ + $field =& $group->fields[$field_id]; + $field->set_caller($this); + //Start field output + $id = 'cnr_field_' . $field->get_id(); + $class = array('cnr_attribute_wrap'); + //If single field in group, check if field title matches group + if ( count($group->fields) == 1 && $group->title == $field->get_property('label') ) + $class[] = 'group_field_title'; + //Add flag to indicate that field was loaded on page + $inc = 'cnr[fields_loaded][' . $field->get_id() . ']'; + $out[] = ''; + $out[] = '
'; + //Build field layout + $out[] = $field->build_layout(); + //end field output + $out[] = '
'; + $field->clear_caller(); + } + $out[] = '
'; //Close fields container + //Add description if exists + if ( !empty($group->description) ) + $out[] = '

' . $group->description . '

'; + } + + //Return group output + return implode($out); + } + + /** + * Set data for a field + * @param string|CNR_Field $field Reference or ID of Field to set data for + * @param mixed $value Data to set + */ + function set_data($field, $value = '') { + if ( 1 == func_num_args() && is_array($field) ) + $this->data = $field; + else { + $field = CNR_Field_Type::get_id($field); + if ( empty($field) ) + return false; + $this->data[$field] = $value; + } + } + + /*-** Admin **-*/ + + /** + * Adds meta boxes for post's content type + * Each group in content type is a separate meta box + * @param string $type Type of item meta boxes are being build for (post, page, link) + * @param string $context Location of meta box (normal, advanced, side) + * @param object $post Post object + */ + function admin_do_meta_boxes($type, $context, $post) { + //Add post data to content type + global $cnr_content_utilities; + $this->set_data($cnr_content_utilities->get_item_data($post)); + + //Get Groups + $groups = array_keys($this->get_groups()); + $priority = 'default'; + //Iterate through groups and add meta box if it fits the context (location) + foreach ( $groups as $group_id ) { + $group =& $this->get_group($group_id); + if ( $context == $group->location && count($group->fields) ) { + //Format ID for meta box + $meta_box_id = $this->prefix . '_group_' . $group_id; + $group_args = array( 'group' => $group_id ); + add_meta_box($meta_box_id, $group->title, $this->m('admin_build_meta_box'), $type, $context, $priority, $group_args); + } + } + } + + /** + * Outputs group fields for a meta box + * @param object $post Post object + * @param array $box Meta box properties + */ + function admin_build_meta_box($post, $box) { + //Stop execution if group not specified + if ( !isset($box['args']['group']) ) + return false; + + //Get ID of group to output + $group_id =& $box['args']['group']; + + $output = array(); + $output[] = '
'; + $output[] = $this->build_group($group_id); + $output[] = '
'; + + //Output group content to screen + echo implode($output); + } + + /** + * Retrieves type ID formatted as a meta value + * @return string + */ + function get_meta_value() { + return serialize(array($this->id)); + } + +} \ No newline at end of file diff --git a/includes/class.content_utilities.php b/includes/class.content_utilities.php new file mode 100644 index 0000000..2e5f4e8 --- /dev/null +++ b/includes/class.content_utilities.php @@ -0,0 +1,1247 @@ +register_hooks(); + } + + /** + * Registers hooks for content types + * @todo 2010-07-30: Check hooks for 3.0 compatibility + */ + function register_hooks() { + //Register types + add_action('init', $this->m('register_types')); + add_action('init', $this->m('add_hooks'), 11); + + //Enqueue scripts for fields in current post type + add_action('admin_enqueue_scripts', $this->m('enqueue_files')); + + //Add menus + //add_action('admin_menu', $this->m('admin_menu')); + + //Build UI on post edit form + add_action('do_meta_boxes', $this->m('admin_do_meta_boxes'), 10, 3); + + //Get edit link for items + //add_filter('get_edit_post_link', $this->m('get_edit_item_url'), 10, 3); + + //add_action('edit_form_advanced', $this->m('admin_page_edit_form')); + + //Save Field data/Content type + add_action('save_post', $this->m('save_item_data'), 10, 2); + + //Modify post query for content type compatibility + add_action('pre_get_posts', $this->m('pre_get_posts'), 20); + } + + /** + * Initialize fields and content types + */ + function register_types() { + //Global variables + global $cnr_field_types, $cnr_content_types; + + /* Field Types */ + + //Base + $base = new CNR_Field_Type('base'); + $base->set_description('Default Element'); + $base->set_property('tag', 'span'); + $base->set_property('class', '', 'attr'); + $base->set_layout('form', '<{tag} name="{field_name}" id="{field_id}" {properties ref_base="root" group="attr"} />'); + $base->set_layout('label', ''); + $base->set_layout('display', '{data format="display"}'); + $this->register_field($base); + + //Base closed + $base_closed = new CNR_Field_Type('base_closed'); + $base_closed->set_parent('base'); + $base_closed->set_description('Default Element (Closed Tag)'); + $base_closed->set_layout('form_start', '<{tag} id="{field_id}" name="{field_name}" {properties ref_base="root" group="attr"}>'); + $base_closed->set_layout('form_end', ''); + $base_closed->set_layout('form', '{form_start ref_base="layout"}{data}{form_end ref_base="layout"}'); + $this->register_field($base_closed); + + //Input + $input = new CNR_Field_Type('input'); + $input->set_parent('base'); + $input->set_description('Default Input Element'); + $input->set_property('tag', 'input'); + $input->set_property('type', 'text', 'attr'); + $input->set_property('value', CNR_Field::USES_DATA, 'attr'); + $this->register_field($input); + + //Text input + $text = new CNR_Field_Type('text', 'input'); + $text->set_description('Text Box'); + $text->set_property('size', 15, 'attr'); + $text->set_property('label'); + $text->set_layout('form', '{label ref_base="layout"} {inherit}'); + $this->register_field($text); + + //Checkbox + $checkbox = new CNR_Field_Type('checkbox', 'input'); + $checkbox->set_description('Checkbox'); + $checkbox->set_property('type', 'checkbox', 'attr'); + $checkbox->set_property('label'); + $checkbox->set_property('checked', '', 'attr'); + $checkbox->set_layout('form', '{inherit} {label ref_base="layout"}'); + $this->register_field($checkbox); + + //Textarea + $ta = new CNR_Field_Type('textarea', 'base_closed'); + $ta->set_property('tag', 'textarea'); + $ta->set_property('cols', 40, 'attr'); + $ta->set_property('rows', 3, 'attr'); + $this->register_field($ta); + + //Rich Text + $rt = new CNR_Field_Type('richtext', 'textarea'); + $rt->set_layout('form', '
{rich_editor}
'); + $this->register_field($rt); + + //Location + $location = new CNR_Field_Type('location'); + $location->set_description('Geographic Coordinates'); + $location->set_element('latitude', 'text', array( 'size' => 3, 'label' => 'Latitude' )); + $location->set_element('longitude', 'text', array( 'size' => 3, 'label' => 'Longitude' )); + $location->set_layout('form', '{latitude ref_base="elements"}, {longitude ref_base="elements"}'); + $this->register_field($location); + + //Phone + $phone = new CNR_Field_Type('phone'); + $phone->set_description('Phone Number'); + $phone->set_element('area', 'text', array( 'size' => 3 )); + $phone->set_element('prefix', 'text', array( 'size' => 3 )); + $phone->set_element('suffix', 'text', array( 'size' => 4 )); + $phone->set_layout('form', '({area ref_base="elements"}) {prefix ref_base="elements"} - {suffix ref_base="elements"}'); + $this->register_field($phone); + + //Hidden + $hidden = new CNR_Field_Type('hidden'); + $hidden->set_parent('input'); + $hidden->set_description('Hidden Field'); + $hidden->set_property('type', 'hidden'); + $this->register_field($hidden); + + //Span + $span = new CNR_Field_Type('span'); + $span->set_description('Inline wrapper'); + $span->set_parent('base_closed'); + $span->set_property('tag', 'span'); + $span->set_property('value', 'Hello there!'); + $this->register_field($span); + + //Select + $select = new CNR_Field_Type('select'); + $select->set_description('Select tag'); + $select->set_parent('base_closed'); + $select->set_property('tag', 'select'); + $select->set_property('tag_option', 'option'); + $select->set_property('options', array()); + $select->set_layout('form', '{label ref_base="layout"} {form_start ref_base="layout"}{loop data="properties.options" layout="option" layout_data="option_data"}{form_end ref_base="layout"}'); + $select->set_layout('option', '<{tag_option} value="{data_ext id="option_value"}">{data_ext id="option_text"}'); + $select->set_layout('option_data', '<{tag_option} value="{data_ext id="option_value"}" selected="selected">{data_ext id="option_text"}'); + $this->register_field($select); + + //Enable plugins to modify (add, remove, etc.) field types + do_action_ref_array('cnr_register_field_types', array(&$cnr_field_types)); + + //Content Types + + //Enable plugins to add/remove content types + do_action_ref_array('cnr_register_content_types', array(&$cnr_content_types)); + + //Enable plugins to modify content types after they have all been registered + do_action_ref_array('cnr_content_types_registered', array(&$cnr_content_types)); + } + + /** + * Add content type to global array of content types + * @param CNR_Content_Type $ct Content type to register + * @global array $cnr_content_types Content types array + */ + function register_content_type(&$ct) { + //Add content type to CNR array + if ( $this->is_content_type($ct) && !empty($ct->id) ) { + global $cnr_content_types; + $cnr_content_types[$ct->id] =& $ct; + } + //WP Post Type Registration + global $wp_post_types; + if ( !empty($ct->id) && !isset($wp_post_types[$ct->id]) ) + register_post_type($ct->id, $this->build_post_type_args($ct)); + } + + /** + * Generates arguments array for WP Post Type Registration + * @param CNR_Content_Type $ct Content type being registered + * @return array Arguments array + * @todo Enable custom taxonomies + */ + function build_post_type_args(&$ct) { + //Setup labels + + //Build labels + $labels = array ( + 'name' => _( $ct->get_title(true) ), + 'singular_name' => _( $ct->get_title(false) ), + 'all_items' => sprintf( _( 'All %s' ), $ct->get_title(true) ), + ); + + //Action labels + $item_actions = array( + 'add_new' => 'Add New %s', + 'edit' => 'Edit %s', + 'new' => 'New %s', + 'view' => 'View %s', + 'search' => array('Search %s', true), + 'not_found' => array('No %s found', true, false), + 'not_found_in_trash' => array('No %s found in Trash', true, false) + ); + + foreach ( $item_actions as $key => $val ) { + $excluded = false; + $plural = false; + if ( is_array($val) ) { + if ( count($val) > 1 && true == $val[1] ) { + $plural = true; + } + if ( count($val) > 2 && false == $val[2] ) + $excluded = true; + $val = $val[0]; + } + $title = ( $plural ) ? $labels['name'] : $labels['singular_name']; + if ( $excluded ) + $item = $key; + else { + $item = $key . '_item' . ( ( $plural ) ? 's' : '' ); + } + $labels[$item] = sprintf($val, $title); + } + + //Setup args + $args = array ( + 'labels' => $labels, + 'description' => $ct->get_description(), + 'public' => true, + 'capability_type' => 'post', + 'rewrite' => array( 'slug' => strtolower($labels['name']) ), + 'has_archive' => true, + 'hierarchical' => false, + 'menu_position' => 5, + 'supports' => array('title', 'editor', 'author', 'thumbnail', 'excerpt', 'trackbacks', 'custom-fields', 'comments', 'revisions'), + 'taxonomies' => get_object_taxonomies('post'), + ); + + return $args; + } + + /** + * Add field type to global array of field types + * @param CNR_Field_Type $field Field to register + * + * @global array $cnr_field_types Field types array + */ + function register_field(&$field) { + if ( $this->is_field($field) && !empty($field->id) ) { + global $cnr_field_types; + $cnr_field_types[$field->id] =& $field; + } + } + + /*-** Helpers **-*/ + + /** + * Checks whether an object is a valid content type instance + * @param obj $ct Object to evaluate + * @return bool TRUE if object is a valid content type instance, FALSE otherwise + */ + function is_content_type(&$ct) { + return is_a($ct, 'cnr_content_type'); + } + + /** + * Checks whether an object is a valid field instance + * @param obj $field Object to evaluate + * @return bool TRUE if object is a valid field instance, FALSE otherwise + */ + function is_field(&$field) { + return is_a($field, 'cnr_field_type'); + } + + /*-** Handlers **-*/ + + /** + * Modifies query parameters to include custom content types + * Adds custom content types to default post query so these items are retrieved as well + * @param WP_Query $q Reference to WP_Query object being used to perform posts query + * @see WP_Query for reference + */ + function pre_get_posts($q) { + $pt =& $q->query_vars['post_type']; + /* Do not continue processing if: + * > In admin section + * > Not main query (or CNR-initiated query) + * > Single object requested + * > More than one post type is already specified + * > Post type other than 'post' is supplied + */ + if ( is_admin() + || ( !$q->is_main_query() && !isset($q->query_vars[$this->get_prefix()]) ) + || $q->is_singular() + || ( is_array($pt) + && ( count($pt) > 1 + || 'post' != $pt[0] ) + ) + || !in_array($pt, array('post', null)) + ) { + return false; + } + + $default_types = $this->get_default_post_types(); + $custom_types = array_diff(array_keys($this->get_types()), $default_types); + if ( !count($custom_types) ) + return false; + //Wrap post type in array + if ( empty($pt) || is_null($pt) ) + $pt = array('post'); + if ( !is_array($pt) ) + $pt = array($pt); + //Add custom types to query + foreach ( $custom_types as $type ) { + $pt[] = $type; + } + } + + /** + * Retrieves current context (content type, action) + * @return array Content Type and Action of current request + */ + function get_context() { + $post = false; + if ( isset($GLOBALS['post']) && !is_null($GLOBALS['post']) ) + $post = $GLOBALS['post']; + elseif ( isset($_REQUEST['post_id']) ) + $post = $_REQUEST['post_id']; + elseif ( isset($_REQUEST['post']) ) + $post = $_REQUEST['post']; + elseif ( isset($_REQUEST['post_type']) ) + $post = $_REQUEST['post_type']; + //Get action + $action = $this->util->get_action(); + if ( empty($post) ) + $post = $this->get_page_type(); + //Get post's content type + $ct =& $this->get_type($post); + + return array(&$ct, $action); + } + + /** + * Enqueues files for fields in current content type + * @param string $page Current context + */ + function enqueue_files($page = null) { + list($ct, $action) = $this->get_context(); + $file_types = array('scripts' => 'script', 'styles' => 'style'); + //Get content type fields + foreach ( $ct->fields as $field ) { + //Enqueue scripts/styles for each field + foreach ( $file_types as $type => $func_base ) { + $deps = $field->{"get_$type"}(); + foreach ( $deps as $handle => $args ) { + //Confirm context + if ( in_array('all', $args['context']) || in_array($page, $args['context']) || in_array($action, $args['context']) ) { + $this->enqueue_file($func_base, $args['params']); + } + } + } + } + } + + /** + * Add plugin hooks for fields used in current request + */ + function add_hooks() { + list($ct, $action) = $this->get_context(); + //Iterate through content type fields and add hooks from fields + foreach ( $ct->fields as $field ) { + //Iterate through hooks added to field + $hooks = $field->get_hooks(); + foreach ( $hooks as $tag => $callback ) { + //Iterate through function callbacks added to tag + foreach ( $callback as $id => $args ) { + //Check if hook/function was already processed + if ( isset($this->hooks_processed[$tag][$id]) ) + continue; + //Add hook/function to list of processed hooks + if ( !isset($this->hooks_processed[$tag]) || !is_array($this->hooks_processed[$tag]) ) + $this->hooks_processed[$tag] = array($id => true); + //Add hook to WP + call_user_func_array('add_filter', $args); + } + } + } + } + + /** + * Enqueues files + * @param string $type Type of file to enqueue (script or style) + * @param array $args (optional) Arguments to pass to enqueue function + */ + function enqueue_file($type = 'script', $args = array()) { + $func = 'wp_enqueue_' . $type; + if ( function_exists($func) ) { + call_user_func_array($func, $args); + } + } + + /** + * Add admin menus for content types + * @deprecated Not needed for 3.0+ + */ + function admin_menu() { + global $cnr_content_types; + + $pos = 21; + foreach ( $cnr_content_types as $id => $type ) { + if ( $this->is_default_post_type($id) ) + continue; + $page = $this->get_admin_page_file($id); + $callback = $this->m('admin_page'); + $access = 8; + $pos += 1; + $title = $type->get_title(true); + if ( !empty($title) ) { + //Main menu + add_menu_page($type->get_title(true), $type->get_title(true), $access, $page, $callback, '', $pos); + //Edit + add_submenu_page($page, __('Edit ' . $type->get_title(true)), __('Edit'), $access, $page, $callback); + $hook = get_plugin_page_hookname($page, $page); + add_action('load-' . $hook, $this->m('admin_menu_load_plugin')); + //Add + $page_add = $this->get_admin_page_file($id, 'add'); + add_submenu_page($page, __('Add New ' . $type->get_title()), __('Add New'), $access, $page_add, $callback); + $hook = get_plugin_page_hook($page_add, $page); + add_action('load-' . $hook, $this->m('admin_menu_load_plugin')); + //Hook for additional menus + $menu_hook = 'cnr_admin_menu_type'; + //Type specific + do_action_ref_array($menu_hook . '_' . $id, array(&$type)); + //General + do_action_ref_array($menu_hook, array(&$type)); + } + } + } + + /** + * Load data for plugin admin page prior to admin-header.php is loaded + * Useful for enqueueing scripts/styles, etc. + */ + function admin_menu_load_plugin() { + //Get Action + global $editing, $post, $post_ID, $p; + $action = $this->util->get_action(); + if ( isset($_GET['delete_all']) ) + $action = 'delete_all'; + if ( isset($_GET['action']) && 'edit' == $_GET['action'] && ! isset($_GET['bulk_edit'])) + $action = 'manage'; + switch ( $action ) { + case 'delete_all' : + case 'edit' : + //Handle bulk actions + //Redirect to edit.php for processing + + //Build query string + $qs = $_GET; + unset($qs['page']); + $edit_uri = admin_url('edit.php') . '?' . build_query($qs); + wp_redirect($edit_uri); + break; + case 'edit-item' : + wp_enqueue_script('admin_comments'); + enqueue_comment_hotkeys_js(); + //Get post being edited + if ( empty($_GET['post']) ) { + wp_redirect("post.php"); //TODO redirect to appropriate manage page + exit(); + } + $post_ID = $p = (int) $_GET['post']; + $post = get_post($post_ID); + if ( !current_user_can('edit_post', $post_ID) ) + wp_die( __('You are not allowed to edit this item') ); + + if ( $last = wp_check_post_lock($post->ID) ) { + add_action('admin_notices', '_admin_notice_post_locked'); + } else { + wp_set_post_lock($post->ID); + $locked = true; + } + //Continue on to add case + case 'add' : + $editing = true; + wp_enqueue_script('autosave'); + wp_enqueue_script('post'); + if ( user_can_richedit() ) + wp_enqueue_script('editor'); + add_thickbox(); + wp_enqueue_script('media-upload'); + wp_enqueue_script('word-count'); + add_action( 'admin_print_footer_scripts', 'wp_tiny_mce', 25 ); + wp_enqueue_script('quicktags'); + wp_enqueue_script($this->add_prefix('edit_form'), $this->util->get_file_url('js/lib.admin.edit_form.js'), array('jquery', 'postbox'), false, true); + break; + default : + wp_enqueue_script( $this->add_prefix('inline-edit-post') ); + } + } + + /** + * Build admin page file name for the specified post type + * @param string|CNR_Content_Type $type Content type ID or object + * @param string $action Action to build file name for + * @param bool $sep_action Whether action should be a separate query variable (Default: false) + * @return string Admin page file name + */ + function get_admin_page_file($type, $action = '', $sep_action = false) { + if ( isset($type->id) ) + $type = $type->id; + $page = $this->add_prefix('post_type_' . $type); + if ( !empty($action) ) { + if ( $sep_action ) + $page .= '&action='; + else + $page .= '-'; + + $page .= $action; + } + return $page; + } + + /** + * Determine content type based on URL query variables + * Uses $_GET['page'] variable to determine content type + * @return string Content type of page (NULL if no type defined by page) + */ + function get_page_type() { + $type = null; + //Extract type from query variable + if ( isset($_GET['page']) ) { + $type = $_GET['page']; + $prefix = $this->add_prefix('post_type_'); + //Remove plugin page prefix + if ( ($pos = strpos($type, $prefix)) === 0 ) + $type = substr($type, strlen($prefix)); + //Remove action (if present) + if ( ($pos = strrpos($type, '-')) && $pos !== false ) + $type = substr($type, 0, $pos); + } + return $type; + } + + /** + * Populate administration page for content type + */ + function admin_page() { + $prefix = $this->add_prefix('post_type_'); + if ( strpos($_GET['page'], $prefix) !== 0 ) + return false; + + //Get action + $action = $this->util->get_action('manage'); + //Get content type + $type =& $this->get_type($this->get_page_type()); + global $title, $parent_file, $submenu_file; + $title = $type->get_title(true); + //$parent_file = $prefix . $type->id; + //$submenu_file = $parent_file; + + switch ( $action ) { + case 'edit-item' : + case 'add' : + $this->admin_page_edit($type, $action); + break; + default : + $this->admin_page_manage($type, $action); + } + } + + /** + * Queries content items for admin management pages + * Also retrieves available post status for specified content type + * @see wp_edit_posts_query + * @param CNR_Content_Type|string $type Content type instance or ID + * @return array All item statuses and Available item status + */ + function admin_manage_query($type = 'post') { + global $wp_query; + $q = array(); + //Get post type + if ( ! is_a($type, 'CNR_Content_Type') ) { + $type = $this->get_type($type); + } + $q = array('post_type' => $type->id); + $g = $_GET; + //Date + $q['m'] = isset($g['m']) ? (int) $g['m'] : 0; + //Category + $q['cat'] = isset($g['cat']) ? (int) $g['cat'] : 0; + $post_stati = array( // array( adj, noun ) + 'publish' => array(_x('Published', 'post'), __('Published posts'), _n_noop('Published (%s)', 'Published (%s)')), + 'future' => array(_x('Scheduled', 'post'), __('Scheduled posts'), _n_noop('Scheduled (%s)', 'Scheduled (%s)')), + 'pending' => array(_x('Pending Review', 'post'), __('Pending posts'), _n_noop('Pending Review (%s)', 'Pending Review (%s)')), + 'draft' => array(_x('Draft', 'post'), _x('Drafts', 'manage posts header'), _n_noop('Draft (%s)', 'Drafts (%s)')), + 'private' => array(_x('Private', 'post'), __('Private posts'), _n_noop('Private (%s)', 'Private (%s)')), + 'trash' => array(_x('Trash', 'post'), __('Trash posts'), _n_noop('Trash (%s)', 'Trash (%s)')), + ); + + $post_stati = apply_filters('post_stati', $post_stati); + + $avail_post_stati = get_available_post_statuses('post'); + + //Status + if ( isset($g['post_status']) && in_array( $g['post_status'], array_keys($post_stati) ) ) { + $q['post_status'] = $g['post_status']; + $q['perm'] = 'readable'; + } else { + unset($q['post_status']); + } + + //Order + if ( isset($q['post_status']) && 'pending' === $q['post_status'] ) { + $q['order'] = 'ASC'; + $q['orderby'] = 'modified'; + } elseif ( isset($q['post_status']) && 'draft' === $q['post_status'] ) { + $q['order'] = 'DESC'; + $q['orderby'] = 'modified'; + } else { + $q['order'] = 'DESC'; + $q['orderby'] = 'date'; + } + + //Pagination + $posts_per_page = (int) get_user_option( 'edit_per_page', 0, false ); + if ( empty( $posts_per_page ) || $posts_per_page < 1 ) + $posts_per_page = 15; + if ( isset($g['paged']) && (int) $g['paged'] > 1 ) + $q['paged'] = (int) $g['paged']; + $q['posts_per_page'] = apply_filters( 'edit_posts_per_page', $posts_per_page ); + //Search + $q[s] = ( isset($g['s']) ) ? $g[s] : ''; + $wp_query->query($q); + + return array($post_stati, $avail_post_stati); + } + + /** + * Counts the number of items in the specified content type + * @see wp_count_posts + * @param CNR_Content_Type|string $type Content Type instance or ID + * @param string $perm Permission level for items (e.g. readable) + * @return array Associative array of item counts by post status (published, draft, etc.) + */ + function count_posts( $type, $perm = '' ) { + global $wpdb; + + $user = wp_get_current_user(); + + if ( !is_a($type, 'CNR_Content_Type') ) + $type = $this->get_type($type); + $type_val = $type->get_meta_value(); + $type = $type->id; + $cache_key = $type; + + //$query = "SELECT post_status, COUNT( * ) AS num_posts FROM {$wpdb->posts} WHERE post_type = %s"; + $query = "SELECT p.post_status, COUNT( * ) as num_posts FROM {$wpdb->posts} p JOIN {$wpdb->postmeta} m ON m.post_id = p.id WHERE m.meta_key = '" . $this->get_type_meta_key() . "' AND m.meta_value = '$type_val'"; + if ( 'readable' == $perm && is_user_logged_in() ) { + //TODO enable check for custom post types "read_private_{$type}s" + if ( !current_user_can("read_private_posts") ) { + $cache_key .= '_' . $perm . '_' . $user->ID; + $query .= " AND (p.post_status != 'private' OR ( p.post_author = '$user->ID' AND p.post_status = 'private' ))"; + } + } + $query .= ' GROUP BY p.post_status'; + + $count = wp_cache_get($cache_key, 'counts'); + if ( false !== $count ) + return $count; + + $count = $wpdb->get_results( $wpdb->prepare( $query, $type ), ARRAY_A ); + + $stats = array( 'publish' => 0, 'private' => 0, 'draft' => 0, 'pending' => 0, 'future' => 0, 'trash' => 0 ); + foreach( (array) $count as $row_num => $row ) { + $stats[$row['post_status']] = $row['num_posts']; + } + + $stats = (object) $stats; + wp_cache_set($cache_key, $stats, 'counts'); + + return $stats; + } + + /** + * Builds management page for items of a specific custom content type + * @param CNR_Content_Type $type Content Type to manage + * @param string $action Current action + * + * @global string $title + * @global string $parent_file + * @global string $plugin_page + * @global string $page_hook + * @global WP_User $current_user + * @global WP_Query $wp_query + * @global wpdb $wpdb + * @global WP_Locale $wp_locale + */ + function admin_page_manage($type, $action) { + if ( !current_user_can('edit_posts') ) + wp_die(__('You do not have sufficient permissions to access this page.')); + + global $title, $parent_file, $plugin_page, $page_hook, $current_user, $wp_query, $wpdb, $wp_locale; + $title = __('Edit ' . $type->get_title(true)); + $admin_path = ABSPATH . 'wp-admin/'; + + //Pagination + if ( ! isset($_GET['paged']) ) + $_GET['paged'] = 1; + + $add_url = $this->get_admin_page_url($type->id, 'add'); + $is_trash = isset($_GET['post_status']) && $_GET['post_status'] == 'trash'; + //User posts + $user_posts = false; + if ( !current_user_can('edit_others_posts') ) { + $user_posts_count = $wpdb->get_var( $wpdb->prepare("SELECT COUNT(1) FROM $wpdb->posts p JOIN $wpdb->postmeta m ON m.post_id = p.id WHERE m.meta_key = '_cnr_post_type' AND m.meta_value = %s AND p.post_status != 'trash' AND p.post_author = %d", $type->get_meta_value(), $current_user->ID) ); + $user_posts = true; + if ( $user_posts_count && empty($_GET['post_status']) && empty($_GET['all_posts']) && empty($_GET['author']) ) + $_GET['author'] = $current_user->ID; + } + //Get content type items + list($post_stati, $avail_post_stati) = $this->admin_manage_query($type->id); + ?> +
+ +

' . __('Search results for “%s”') . '', esc_html( get_search_query() ) ); ?> +

+ +
+ + + + + + +
+ add_query_arg( 'paged', '%#%' ), + 'format' => '', + 'prev_text' => __('«'), + 'next_text' => __('»'), + 'total' => $wp_query->max_num_pages, + 'current' => $_GET['paged'] + )); + ?> +
+ + + + + posts p JOIN $wpdb->postmeta m ON m.post_id = p.ID WHERE m.meta_key = '" . $this->get_type_meta_key() . "' AND m.meta_value = '" . $type->get_meta_value() . "' ORDER BY post_date DESC"; + + $arc_result = $wpdb->get_results( $arc_query ); + + $month_count = count($arc_result); + + if ( $month_count && !( 1 == $month_count && 0 == $arc_result[0]->mmonth ) ) { + $m = isset($_GET['m']) ? (int)$_GET['m'] : 0; + ?> + + __('View all categories'), 'hide_empty' => 0, 'hierarchical' => 1, + 'show_count' => 0, 'orderby' => 'name', 'selected' => $cat); + wp_dropdown_categories($dropdown_options); + do_action('restrict_manage_posts'); + ?> + + + + +
+ + +
' . __( 'Displaying %s–%s of %s' ) . '%s', + number_format_i18n( ( $_GET['paged'] - 1 ) * $wp_query->query_vars['posts_per_page'] + 1 ), + number_format_i18n( min( $_GET['paged'] * $wp_query->query_vars['posts_per_page'], $wp_query->found_posts ) ), + number_format_i18n( $wp_query->found_posts ), + $page_links + ); echo $page_links_text; ?>
+ +
+
+ +
+

+ +
+ +
+
+
+ get_title()); + $admin_path = ABSPATH . 'wp-admin/'; + include ($admin_path . 'edit-form-advanced.php'); + } + + /** + * Adds hidden field declaring content type on post edit form + * @deprecated no longer needed for WP 3.0+ + */ + function admin_page_edit_form() { + global $post, $plugin_page; + if ( empty($post) || !$post->ID ) { + $type = $this->get_type($post); + if ( ! empty($type) && ! empty($type->id) ) { + ?> + + get_types()), array('post', 'page'))) ) { + //Get content type definition + $ct =& $this->get_type($post); + //Pass processing to content type instance + $ct->admin_do_meta_boxes($type, $context, $post); + } + } + + /** + * Saves field data submitted for current post + * @param int $post_id ID of current post + * @param object $post Post object + */ + function save_item_data($post_id, $post) { + if ( empty($post_id) || empty($post) || !isset($_POST['cnr']) || !is_array($_POST['cnr']) ) + return false; + $pdata = $_POST['cnr']; + + if ( isset($pdata['attributes']) && is_array($pdata['attributes']) && isset($pdata['fields_loaded']) && is_array($pdata['fields_loaded']) ) { + + $prev_data = (array) $this->get_item_data($post_id); + + //Remove loaded fields from prev data + $prev_data = array_diff_key($prev_data, $pdata['fields_loaded']); + + //Get current field data + $curr_data = $pdata['attributes']; + + //Merge arrays together (new data overwrites old data) + if ( is_array($prev_data) && is_array($curr_data) ) { + $curr_data = array_merge($prev_data, $curr_data); + } + + //Save to database + update_post_meta($post_id, $this->get_fields_meta_key(), $curr_data); + } + //Save content type + if ( isset($_POST['cnr']['content_type']) ) { + $type = $_POST['cnr']['content_type']; + $saved_type = get_post_meta($post_id, $this->get_type_meta_key(), true); + if ( is_array($saved_type) ) + $saved_type = implode($saved_type); + if ( $type != $saved_type ) { + //Continue processing if submitted content type is different from previously-saved content type (or no type was previously set) + update_post_meta($post_id, $this->get_type_meta_key(), array($type)); + } + } + } + + /*-** Helpers **-*/ + + /** + * Get array of default post types + * @return array Default post types + */ + function get_default_post_types() { + return array('post', 'page', 'attachment', 'revision', 'nav_menu'); + } + + /** + * Checks if post's post type is a standard WP post type + * @param mixed $post_type Post type (default) or post ID/object to evaluate + * @see CNR_Content_Utilities::get_type() for possible parameter values + * @return bool TRUE if post is default type, FALSE if it is a custom type + */ + function is_default_post_type($post_type) { + if ( !is_string($post_type) ) { + $post_type = $this->get_type($post_type); + $post_type = $post_type->id; + } + return in_array($post_type, $this->get_default_post_types()); + } + + /** + * Checks if specified content type has been defined + * @param string|CNR_Content_Type $type Content type ID or object + * @return bool TRUE if content type exists, FALSE otherwise + * + * @uses array $cnr_content_types + */ + function type_exists($type) { + global $cnr_content_types; + if ( ! is_scalar($type) ) { + if ( is_a($type, 'CNR_Content_Type') ) + $type = $type->id; + else + $type = null; + } + return ( isset($cnr_content_types[$type]) ); + } + + /** + * Retrieves content type definition for specified content item (post, page, etc.) + * If content type does not exist, a new instance object will be created and returned + * > New content types are automatically registered (since we are looking for registered types when using this method) + * @param string|object $item Post object, or item type (string) + * @return CNR_Content_Type Reference to matching content type, empty content type if no matching type exists + * + * @uses array $cnr_content_types + */ + function &get_type($item) { + //Return immediately if $item is a content type instance + if ( is_a($item, 'CNR_Content_Type') ) + return $item; + + $type = null; + + if ( is_string($item) ) + $type = $item; + + if ( !$this->type_exists($type) ) { + $post = $item; + + //Check if $item is a post (object or ID) + if ( $this->util->check_post($post) && isset($post->post_type) ) { + $type = $post->post_type; + } + } + global $cnr_content_types; + if ( $this->type_exists($type) ) { + //Retrieve content type from global array + $type =& $cnr_content_types[$type]; + } else { + //Create new empty content type if it does not already exist + $type = new CNR_Content_Type($type); + //Automatically register newly initialized content type if it extends an existing WP post type + if ( $this->is_default_post_type($type->id) ) + $type->register(); + } + + return $type; + } + + /** + * Retrieve content types + * @return Reference to content types array + */ + function &get_types() { + return $GLOBALS['cnr_content_types']; + } + + /** + * Retrieve meta key for post fields + * @return string Fields meta key + */ + function get_fields_meta_key() { + return $this->util->make_meta_key('fields'); + } + + /** + * Retrieve meta key for post type + * @return string Post type meta key + */ + function get_type_meta_key() { + return $this->util->make_meta_key('post_type'); + } + + /** + * Checks if post contains specified field data + * @param Object $post (optional) Post to check data for + * @param string $field (optional) Field ID to check for + * @return bool TRUE if data exists, FALSE otherwise + */ + function has_item_data($item = null, $field = null) { + $ret = $this->get_item_data($item, $field, 'raw', null); + if ( is_scalar($ret) ) + return ( !empty($ret) || $ret === 0 ); + if ( is_array($ret) ) { + foreach ( $ret as $key => $val ) { + if ( !empty($val) || $val === 0 ) + return true; + } + } + return false; + } + + /** + * Retrieve specified field data from content item (e.g. post) + * Usage Examples: + * get_item_data($post_id, 'field_id') + * - Retrieves field_id data from global $post object + * - Field data is formatted using 'display' layout of field + * + * get_item_data($post_id, 'field_id', 'raw') + * - Retrieves field_id data from global $post object + * - Raw field data is returned (no formatting) + * + * get_item_data($post_id, 'field_id', 'display', $post_id) + * - Retrieves field_id data from post matching $post_id + * - Field data is formatted using 'display' layout of field + * + * get_item_data($post_id, 'field_id', null) + * - Retrieves field_id data from post matching $post_id + * - Field data is formatted using 'display' layout of field + * - The default layout is used when no valid layout is specified + * + * get_item_data($post_id) + * - Retrieves full data array from post matching $post_id + * + * @param int|object $item(optional) Content item to retrieve field from (Default: null - global $post object will be used) + * @param string $field ID of field to retrieve + * @param string $layout(optional) Layout to use when returning field data (Default: display) + * @param array $attr (optional) Additional attributes to pass along to field object (e.g. for building layout, etc.) + * @see CNR_Field_Type::build_layout for more information on attribute usage + * @return mixed Specified field data + */ + function get_item_data($item = null, $field = null, $layout = null, $default = '', $attr = null) { + $ret = $default; + + //Get item + $item = get_post($item); + + if ( !isset($item->ID) ) + return $ret; + + //Get item data + $data = get_post_meta($item->ID, $this->get_fields_meta_key(), true); + + //Get field data + + //Set return value to data if no field specified + if ( empty($field) || !is_string($field) ) + $ret = $data; + //Stop if no valid field specified + if ( !isset($data[$field]) ) { + //TODO Check $item object to see if specified field exists (e.g. title, post_status, etc.) + return $ret; + } + + $ret = $data[$field]; + + //Initialize layout value + $layout_def = 'display'; + + if ( !is_scalar($layout) || empty($layout) ) + $layout = $layout_def; + + $layout = strtolower($layout); + + //Check if raw data requested + if ( 'raw' == $layout ) + return $ret; + + /* Build specified layout */ + + //Get item's content type + $ct =& $this->get_type($item); + $ct->set_data($data); + + //Get field definition + $fdef =& $ct->get_field($field); + + //Validate layout + if ( !$fdef->has_layout($layout) ) + $layout = $layout_def; + + //Build layout + $fdef->set_caller($ct); + $ret = $fdef->build_layout($layout, $attr); + $fdef->clear_caller(); + + //Return formatted value + return $ret; + } + + /** + * Prints an item's field data + * @see CNR_Content_Utilities::get_item_data() for more information + * @param int|object $item(optional) Content item to retrieve field from (Default: null - global $post object will be used) + * @param string $field ID of field to retrieve + * @param string $layout(optional) Layout to use when returning field data (Default: display) + * @param mixed $default (optional) Default value to return in case of errors, etc. + * @param array $attr Additional attributes to pass to field + */ + function the_item_data($item = null, $field = null, $layout = null, $default = '', $attr = null) { + echo apply_filters('cnr_the_item_data', $this->get_item_data($item, $field, $layout, $default, $attr), $item, $field, $layout, $default, $attr); + } + + /** + * Build Admin URL for specified post type + * @param string|CNR_Content_Type $type Content type ID or object + * @param string $action Action to build URL for + * @param bool $sep_action Whether action should be a separate query variable (Default: false) + * @return string Admin page URL + */ + function get_admin_page_url($type, $action = '', $sep_action = false) { + $url = admin_url('admin.php'); + $url .= '?page=' . $this->get_admin_page_file($type, $action, $sep_action); + return $url; + } + + function get_edit_item_url($edit_url, $item_id, $context) { + //Get post type + $type = $this->get_type($item_id); + if ( ! $this->is_default_post_type($type->id) && $this->type_exists($type) ) { + $edit_url = $this->get_admin_page_url($type, 'edit-item', true) . '&post=' . $item_id; + } + + return $edit_url; + } +} \ No newline at end of file diff --git a/includes/class.field.php b/includes/class.field.php new file mode 100644 index 0000000..03461bb --- /dev/null +++ b/includes/class.field.php @@ -0,0 +1,4 @@ + Group Name + * -> Property Name => Null + * Reason: Faster searching over large arrays + * @var array Groupings of Properties + */ + var $property_groups = array(); + + /** + * @var array Field type layouts + */ + var $layout = array(); + + /** + * @var CNR_Field_Type Parent field type (reference) + */ + var $parent = null; + + /** + * Object that field is in + * @var CNR_Field|CNR_Field_Type|CNR_Content_Type + */ + var $container = null; + + /** + * Object that called field + * Used to determine field hierarchy/nesting + * @var CNR_Field|CNR_Field_Type|CNR_Content_Type + */ + var $caller = null; + + /** + * Constructor + */ + function __construct($id = '', $parent = null) { + parent::__construct($id); + + $this->id = $id; + $this->set_parent($parent); + } + + /* Getters/Setters */ + + /** + * Search for specified member value in field's container object (if exists) + * @param string $member Name of object member to search (e.g. properties, layout, etc.) + * @param string $name Value to retrieve from member + * @return mixed Member value if found (Default: empty string) + */ + function get_container_value($member, $name = '', $default = '') { + $container =& $this->get_container(); + return $this->get_object_value($container, $member, $name, $default, 'container'); + } + + /** + * Search for specified member value in field's container object (if exists) + * @param string $member Name of object member to search (e.g. properties, layout, etc.) + * @param string $name Value to retrieve from member + * @return mixed Member value if found (Default: empty string) + */ + function get_caller_value($member, $name = '', $default = '') { + $caller =& $this->get_caller(); + return $this->get_object_value($caller, $member, $name, $default, 'caller'); + } + + /** + * Sets reference to container object of current field + * Reference is cleared if no valid object is passed to method + * @param object $container + */ + function set_container(&$container) { + if ( !empty($container) && is_object($container) ) { + //Set as param as container for current field + $this->container =& $container; + } else { + //Clear container member if argument is invalid + $this->clear_container(); + } + } + + /** + * Clears reference to container object of current field + */ + function clear_container() { + $this->container = null; + } + + /** + * Retrieves reference to container object of current field + * @return object Reference to container object + */ + function &get_container() { + $ret = null; + if ( $this->has_container() ) + $ret =& $this->container; + return $ret; + } + + /** + * Checks if field has a container reference + * @return bool TRUE if field is contained, FALSE otherwise + */ + function has_container() { + return !empty($this->container); + } + + /** + * Sets reference to calling object of current field + * Any existing reference is cleared if no valid object is passed to method + * @param object $caller Calling object + */ + function set_caller(&$caller) { + if ( !empty($caller) && is_object($caller) ) + $this->caller =& $caller; + else + $this->clear_caller(); + } + + /** + * Clears reference to calling object of current field + */ + function clear_caller() { + unset($this->caller); + } + + /** + * Retrieves reference to caller object of current field + * @return object Reference to caller object + */ + function &get_caller() { + $ret = null; + if ( $this->has_caller() ) + $ret =& $this->caller; + return $ret; + } + + /** + * Checks if field has a caller reference + * @return bool TRUE if field is called by another field, FALSE otherwise + */ + function has_caller() { + return !empty($this->caller); + } + + /** + * Add/Set a property on the field definition + * @param string $name Name of property + * @param mixed $value Default value for property + * @param string|array $group Group(s) property belongs to + * @return boolean TRUE if property is successfully added to field type, FALSE otherwise + */ + function set_property($name, $value = '', $group = null) { + //Do not add if property name is not a string + if ( !is_string($name) ) + return false; + //Create property array + $prop_arr = array(); + $prop_arr['value'] = $value; + //Add to properties array + $this->properties[$name] = $value; + //Add property to specified groups + if ( !empty($group) ) { + $this->set_group_property($group, $name); + } + return true; + } + + /** + * Sets multiple properties on field type at once + * @param array $properties Properties. Each element is an array containing the arguments to set a new property + * @return boolean TRUE if successful, FALSE otherwise + */ + function set_properties($properties) { + if ( !is_array($properties) ) + return false; + foreach ( $properties as $name => $val) { + $this->set_property($name, $val); + } + } + + /** + * Retreives property from field type + * @param string $name Name of property to retrieve + * @return mixed Specified Property if exists (Default: Empty string) + */ + function get_property($name) { + $val = $this->get_member_value('properties', $name); + return $val; + } + + /** + * Adds Specified Property to a Group + * @param string|array $group Group(s) to add property to + * @param string $property Property to add to group + */ + function set_group_property($group, $property) { + if ( is_string($group) && isset($this->property_groups[$group][$property]) ) + return; + if ( !is_array($group) ) { + $group = array($group); + } + + foreach ($group as $g) { + $g = trim($g); + //Initialize group if it doesn't already exist + if ( !isset($this->property_groups[$g]) ) + $this->property_groups[$g] = array(); + + //Add property to group + $this->property_groups[$g][$property] = null; + } + } + + /** + * Retrieve property group + * @param string $group Group to retrieve + * @return array Array of properties in specified group + */ + function get_group($group) { + return $this->get_member_value('property_groups', $group, array()); + } + + /** + * Sets an element for the field type + * @param string $name Name of element + * @param CNR_Field_Type $type Reference of field type to use for element + * @param array $properties Properties for element (passed as keyed associative array) + * @param string $id_prop Name of property to set $name to (e.g. ID, etc.) + */ + function set_element($name, $type, $properties = array(), $id_prop = 'id') { + $name = trim(strval($name)); + if ( empty($name) ) + return false; + //Create new field for element + $el = new CNR_Field($name, $type); + //Set container to current field instance + $el->set_container($this); + //Add properties to element + $el->set_properties($properties); + //Save element to current instance + $this->elements[$name] =& $el; + } + + /** + * Add a layout to the field + * @param string $name Name of layout + * @param string $value Layout text + */ + function set_layout($name, $value = '') { + if ( !is_string($name) ) + return false; + $name = trim($name); + $this->layout[$name] = $value; + return true; + } + + /** + * Retrieve specified layout + * @param string $name Layout name + * @param bool $parse_nested (optional) Whether nested layouts should be expanded in retreived layout or not (Default: TRUE) + * @return string Specified layout text + */ + function get_layout($name = 'form', $parse_nested = true) { + //Retrieve specified layout (use $name value if no layout by that name exists) + $layout = $this->get_member_value('layout', $name, $name); + + //Find all nested layouts in current layout + if ( !empty($layout) && !!$parse_nested ) { + $ph = $this->get_placeholder_defaults(); + + while ($ph->match = $this->parse_layout($layout, $ph->pattern_layout)) { + //Iterate through the different types of layout placeholders + foreach ($ph->match as $tag => $instances) { + //Iterate through instances of a specific type of layout placeholder + foreach ($instances as $instance) { + //Get nested layout + $nested_layout = $this->get_member_value($instance); + + //Replace layout placeholder with retrieved item data + if ( !empty($nested_layout) ) + $layout = str_replace($ph->start . $instance['match'] . $ph->end, $nested_layout, $layout); + } + } + } + } + + return $layout; + } + + /** + * Checks if specified layout exists + * Finds layout if it exists in current object or any of its parents + * @param string $layout Name of layout to check for + * @return bool TRUE if layout exists, FALSE otherwise + */ + function has_layout($layout) { + $ret = false; + if ( is_string($layout) && ($layout = trim($layout)) && !empty($layout) ) { + $layout = $this->get_member_value('layout', $layout, false); + if ( $layout !== false ) + $ret = true; + } + + return $ret; + } + + /** + * Checks if layout content is valid + * Layouts need to have placeholders to be valid + * @param string $layout_content Layout content (markup) + * @return bool TRUE if layout is valid, FALSE otherwise + */ + function is_valid_layout($layout_content) { + $ph = $this->get_placeholder_defaults(); + return preg_match($ph->pattern_general, $layout_content); + } + + /** + * Parse field layout with a regular expression + * @param string $layout Layout data + * @param string $search Regular expression pattern to search layout for + * @return array Associative array containing all of the regular expression matches in the layout data + * Array Structure: + * root => placeholder tags + * => Tag instances (array) + * 'tag' => (string) tag name + * 'match' => (string) placeholder match + * 'attributes' => (array) attributes + */ + function parse_layout($layout, $search) { + $ph_xml = ''; + $parse_match = ''; + $ph_root_tag = 'ph_root_element'; + $ph_start_xml = '<'; + $ph_end_xml = ' />'; + $ph_wrap_start = '<' . $ph_root_tag . '>'; + $ph_wrap_end = ''; + $parse_result = false; + + //Find all nested layouts in layout + $match_value = preg_match_all($search, $layout, $parse_match, PREG_PATTERN_ORDER); + + if ($match_value !== false && $match_value > 0) { + $parse_result = array(); + //Get all matched elements + $parse_match = $parse_match[1]; + + //Build XML string from placeholders + foreach ($parse_match as $ph) { + $ph_xml .= $ph_start_xml . $ph . $ph_end_xml . ' '; + } + $ph_xml = $ph_wrap_start . $ph_xml . $ph_wrap_end; + //Parse XML data + $ph_prs = xml_parser_create(); + xml_parser_set_option($ph_prs, XML_OPTION_SKIP_WHITE, 1); + xml_parser_set_option($ph_prs, XML_OPTION_CASE_FOLDING, 0); + $ret = xml_parse_into_struct($ph_prs, $ph_xml, $parse_result['values'], $parse_result['index']); + xml_parser_free($ph_prs); + + //Build structured array with all parsed data + + unset($parse_result['index'][$ph_root_tag]); + + //Build structured array + $result = array(); + foreach ($parse_result['index'] as $tag => $instances) { + $result[$tag] = array(); + //Instances + foreach ($instances as $instance) { + //Skip instance if it doesn't exist in parse results + if (!isset($parse_result['values'][$instance])) + continue; + + //Stop processing instance if a previously-saved instance with the same options already exists + foreach ($result[$tag] as $tag_match) { + if ($tag_match['match'] == $parse_match[$instance - 1]) + continue 2; + } + + //Init instance data array + $inst_data = array(); + + //Add Tag to array + $inst_data['tag'] = $parse_result['values'][$instance]['tag']; + + //Add instance data to array + $inst_data['attributes'] = (isset($parse_result['values'][$instance]['attributes'])) ? $inst_data['attributes'] = $parse_result['values'][$instance]['attributes'] : ''; + + //Add match to array + $inst_data['match'] = $parse_match[$instance - 1]; + + //Add to result array + $result[$tag][] = $inst_data; + } + } + $parse_result = $result; + } + + return $parse_result; + } + + /** + * Retrieves default properties to use when evaluating layout placeholders + * @return object Object with properties for evaluating layout placeholders + */ + function get_placeholder_defaults() { + $ph = new stdClass(); + $ph->start = '{'; + $ph->end = '}'; + $ph->reserved = array('ref' => 'ref_base'); + $ph->pattern_general = '/' . $ph->start . '([a-zA-Z0-9_].*?)' . $ph->end . '/i'; + $ph->pattern_layout = '/' . $ph->start . '([a-zA-Z0-9].*?\s+' . $ph->reserved['ref'] . '="layout.*?".*?)' . $ph->end . '/i'; + return $ph; + } + + /** + * Builds HTML for a field based on its properties + * @param array $field Field properties (id, field, etc.) + * @param array data Additional data for current field + */ + function build_layout($layout = 'form', $data = null) { + $out_default = ''; + + /* Layout */ + + //Get base layout + $out = $this->get_layout($layout); + + //Only parse valid layouts + if ( $this->is_valid_layout($out) ) { + //Parse Layout + $ph = $this->get_placeholder_defaults(); + + //Search layout for placeholders + while ( $ph->match = $this->parse_layout($out, $ph->pattern_general) ) { + //Iterate through placeholders (tag, id, etc.) + foreach ( $ph->match as $tag => $instances ) { + //Iterate through instances of current placeholder + foreach ( $instances as $instance ) { + //Process value based on placeholder name + $target_property = apply_filters('cnr_process_placeholder_' . $tag, '', $this, $instance, $layout, $data); + + //Process value using default processors (if necessary) + if ( '' == $target_property ) { + $target_property = apply_filters('cnr_process_placeholder', $target_property, $this, $instance, $layout, $data); + } + + //Clear value if value not a string + if ( !is_scalar($target_property) ) { + $target_property = ''; + } + //Replace layout placeholder with retrieved item data + $out = str_replace($ph->start . $instance['match'] . $ph->end, $target_property, $out); + } + } + } + } else { + $out = $out_default; + } + + /* Return generated value */ + + return $out; + } + + /*-** Static Methods **-*/ + + /** + * Returns indacator to use field data (in layouts, property values, etc.) + */ + function uses_data() { + return self::USES_DATA; + } + + /** + * Register a function to handle a placeholder + * Multiple handlers may be registered for a single placeholder + * Basically a wrapper function to facilitate adding hooks for placeholder processing + * @uses add_filter() + * @param string $placeholder Name of placeholder to add handler for (Using 'all' will set the function as a handler for all placeholders + * @param callback $handler Function to set as a handler + * @param int $priority (optional) Priority of handler + */ + static function register_placeholder_handler($placeholder, $handler, $priority = 10) { + if ( 'all' == $placeholder ) + $placeholder = ''; + else + $placeholder = '_' . $placeholder; + + add_filter('cnr_process_placeholder' . $placeholder, $handler, $priority, 5); + } + + /** + * Default placeholder processing + * To be executed when current placeholder has not been handled by another handler + * @param string $ph_output Value to be used in place of placeholder + * @param CNR_Field $field Field containing placeholder + * @param array $placeholder Current placeholder + * @see CNR_Field::parse_layout for structure of $placeholder array + * @param string $layout Layout to build + * @param array $data Extended data for field + * @return string Value to use in place of current placeholder + */ + function process_placeholder_default($ph_output, $field, $placeholder, $layout, $data) { + //Validate parameters before processing + if ( empty($ph_output) && is_a($field, 'CNR_Field_Type') && is_array($placeholder) ) { + //Build path to replacement data + $ph_output = $field->get_member_value($placeholder); + + //Check if value is group (properties, etc.) + //All groups must have additional attributes (beyond reserved attributes) that define how items in group are used + if (is_array($ph_output) + && !empty($placeholder['attributes']) + && is_array($placeholder['attributes']) + && ($ph = $field->get_placeholder_defaults()) + && $attribs = array_diff(array_keys($placeholder['attributes']), array_values($ph->reserved)) + ) { + /* Targeted property is an array, but the placeholder contains additional options on how property is to be used */ + + //Find items matching criteria in $ph_output + //Check for group criteria + //TODO: Implement more robust/flexible criteria handling (2010-03-11: Currently only processes property groups) + if ( 'properties' == $placeholder['tag'] && ($prop_group = $field->get_group($placeholder['attributes']['group'])) && !empty($prop_group) ) { + /* Process group */ + $group_out = array(); + //Iterate through properties in group and build string + foreach ( $prop_group as $prop_key => $prop_val ) { + $group_out[] = $prop_key . '="' . $field->get_property($prop_key) . '"'; + } + $ph_output = implode(' ', $group_out); + } + } elseif ( is_object($ph_output) && is_a($ph_output, $field->base_class) ) { + /* Targeted property is actually a nested field */ + //Set caller to current field + $ph_output->set_caller($field); + //Build layout for nested element + $ph_output = $ph_output->build_layout($layout); + } + } + + return $ph_output; + } + + /** + * Build Field ID attribute + * @see CNR_Field_Type::process_placeholder_default for parameter descriptions + * @return string Placeholder output + */ + function process_placeholder_id($ph_output, $field, $placeholder, $layout, $data) { + //Get attributes + $args = wp_parse_args($placeholder['attributes'], array('format' => 'attr_id')); + return $field->get_id($args); + } + + /** + * Build Field name attribute + * Name is formatted as an associative array for processing by PHP after submission + * @see CNR_Field_Type::process_placeholder_default for parameter descriptions + * @return string Placeholder output + */ + function process_placeholder_name($ph_output, $field, $placeholder, $layout, $data) { + //Get attributes + $args = wp_parse_args($placeholder['attributes'], array('format' => 'default')); + return $field->get_id($args); + } + + /** + * Retrieve data for field + * @see CNR_Field_Type::process_placeholder_default for parameter descriptions + * @return string Placeholder output + */ + function process_placeholder_data($ph_output, $field, $placeholder, $layout) { + $val = $field->get_data(); + if ( !is_null($val) ) { + $ph_output = $val; + $attr =& $placeholder['attributes']; + //Get specific member in value (e.g. value from a specific field element) + if ( isset($attr['element']) && is_array($ph_output) && ( $el = $attr['element'] ) && isset($ph_output[$el]) ) + $ph_output = $ph_output[$el]; + if ( isset($attr['format']) && 'display' == $attr['format'] ) + $ph_output = nl2br($ph_output); + } + + //Return data + return $ph_output; + } + + /** + * Loops over data to build field output + * Options: + * data - Dot-delimited path in field that contains data to loop through + * layout - Name of layout to use for each data item in loop + * layout_data - Name of layout to use for data item that matches previously-saved field data + * @see CNR_Field_Type::process_placeholder_default for parameter descriptions + * @return string Placeholder output + */ + function process_placeholder_loop($ph_output, $field, $placeholder, $layout, $data) { + //Setup loop options + $attr_defaults = array ( + 'layout' => '', + 'layout_data' => null, + 'data' => '' + ); + + $attr = wp_parse_args($placeholder['attributes'], $attr_defaults); + + if ( is_null($attr['layout_data']) ) { + $attr['layout_data'] =& $attr['layout']; + } + + //Get data for loop + $path = explode('.', $attr['data']); + $loop_data = $field->get_member_value($path); + /*if ( isset($loop_data['value']) ) + $loop_data = $loop_data['value']; + */ + $out = array(); + + //Get field data + $data = $field->get_data(); + + //Iterate over data and build output + if ( is_array($loop_data) && !empty($loop_data) ) { + foreach ( $loop_data as $value => $label ) { + //Load appropriate layout based on field value + $layout = ( ($data === 0 && $value === $data) xor $data == $value ) ? $attr['layout_data'] : $attr['layout']; + //Stop processing if no valid layout is returned + if ( empty($layout) ) + continue; + //Prep extended field data + $data_ext = array('option_value' => $value, 'option_text' => $label); + $out[] = $field->build_layout($layout, $data_ext); + } + } + + //Return output + return implode($out); + } + + /** + * Returns specified value from extended data array for field + * @see CNR_Field_Type::process_placeholder_default for parameter descriptions + * @return string Placeholder output + */ + function process_placeholder_data_ext($ph_output, $field, $placeholder, $layout, $data) { + if ( isset($placeholder['attributes']['id']) && ($key = $placeholder['attributes']['id']) && isset($data[$key]) ) { + $ph_output = strval($data[$key]); + } + + return $ph_output; + } + + /** + * WP Editor + * @see CNR_Field_Type::process_placeholder_default for parameter descriptions + * @return string Placeholder output + */ + function process_placeholder_rich_editor($ph_output, $field, $placeholder, $layout, $data) { + $id = $field->get_id( array ( + 'format' => 'attr_id' + )); + $settings = array ( + 'textarea_name' => $field->get_id( array ( + 'format' => 'default' + )) + ); + ob_start(); + wp_editor($field->get_data(), $id, $settings); + $out = ob_get_clean(); + return $out; + } + +} \ No newline at end of file From c13c0a76e3d2f5343c527cc67de70dfb9287e9ce Mon Sep 17 00:00:00 2001 From: SM Date: Thu, 16 Jul 2015 09:08:58 -1000 Subject: [PATCH 13/24] Refactor: Split classes into separate Files (Posts) --- includes/class.post.php | 248 ++++ .../{class.posts.php => class.post_query.php} | 1131 +++++++---------- 2 files changed, 688 insertions(+), 691 deletions(-) create mode 100644 includes/class.post.php rename includes/{class.posts.php => class.post_query.php} (60%) diff --git a/includes/class.post.php b/includes/class.post.php new file mode 100644 index 0000000..e30ee77 --- /dev/null +++ b/includes/class.post.php @@ -0,0 +1,248 @@ + array ( + 'file' => 'js/lib.posts.js', + 'deps' => '[core]', + 'context' => 'admin' + ), + ); + + /** + * Default title separator + * @var string + */ + var $title_sep = '‹'; + + /*-** Initialization **-*/ + + function register_hooks() { + parent::register_hooks(); + + //Template + // add_filter('wp_title', $this->m('page_title'), 11, 3); + + //Admin + add_action('admin_head', $this->m('admin_set_title'), 11); + } + + /** + * Gets entire parent tree of post as an array + * + * Array order is from top level to immediate post parent + * @static + * @param object $post Post to get path for + * @param string $prop Property to retrieve from parents. If specified, array will contain only this property from parents + * @param $depth Unused + * @return array of Post Objects/Properties + */ + static function get_parents($post, $prop = '', $depth = '') { + $parents = get_post_ancestors($post = get_post($post, OBJECT, '')); + if ( is_object($post) && !empty($parents) && ('id' != strtolower(trim($prop))) ) { + //Retrieve post data for parents if full data or property other than post ID is required + $args = array( + 'include' => $parents, + 'post_type' => 'any', + ); + $ancestors = get_posts($args); + + //Sort array in ancestor order + $temp_parents = array(); + foreach ($ancestors as $ancestor) { + //Get index of ancestor + $i = array_search($ancestor->ID, $parents); + if ( false === $i ) + continue; + //Insert post at index + $temp_parents[$i] = $ancestor; + } + + if ( !empty($temp_parents) ) + $parents = $temp_parents; + } + //Reverse Array (to put top level parent at beginning of array) + $parents = array_reverse($parents); + return $parents; + } + + /** + * Get the IDs of a collection of posts + * @return array IDs of Posts passed to function + * @param array $posts Array of Post objects + */ + function get_ids($posts) { + $callback = create_function('$post', 'return $post->ID;'); + $arr_ids = array_map($callback, $posts); + return $arr_ids; + } + + /*-** Children **-*/ + + /** + * Gets children posts of specified page and stores them for later use + * This method hooks into 'the_posts' filter to retrieve child posts for any single page retrieved by WP + * @param int|object $post ID or Object of Post to get children for + * @return CNR_Post_Query $posts Posts array (required by 'the_posts' filter) + * + * @global WP_Query $wp_query + */ + function &get_children($post = null) { + //Global variables + global $wp_query; + $children = new CNR_Post_Query(); + if ( empty($post) && !empty($wp_query->posts) ) + $post = $wp_query->posts[0]; + + if ( is_object($post) ) + $post = $post->ID; + if ( is_numeric($post) ) + $post = (int) $post; + else + return $children; + + //Get children posts of page + if ( $post ) { + //Set arguments to retrieve children posts of current page + $limit = ( is_feed() ) ? get_option('posts_per_rss') : get_option('posts_per_page'); + $offset = ( is_paged() ) ? ( (get_query_var('paged') - 1) * $limit ) : 0; + $c_args = array( + 'post_parent' => $post, + 'numberposts' => $limit, + 'offset' => $offset + ); + + //Create post query object + $children->set_arg($c_args); + + //Get children posts + $children->get(); + } + + return $children; + } + + /*-** Post Metadata **-*/ + + /** + * Retrieves the post's section data + * @param string $data (optional) Section data to return (Default: full section object) + * Possible values: + * NULL Full section post object + * Column name Post column data (if exists) + * + * @return mixed post's section data (Default: ID value) + */ + function get_section($post = null, $data = null) { + $p = get_post($post); + $retval = 0; + if ( is_object($p) && isset($p->post_parent) ) + $retval = intval($p->post_parent); + + //Get specified section data for posts with valid parents + if ( $retval > 0 ) { + if ( !empty($data) ) { + $retval = get_post_field($data, $retval); + } else { + $retval = get_post($retval); + } + } + + return $retval; + } + + /** + * Prints the post's section data + * @uses CNR_Post::get_section() + * @param string $type (optional) Type of data to return (Default: ID) + */ + function the_section($post = null, $data = 'ID') { + if ( empty($data) ) + $data = 'ID'; + echo CNR_Post::get_section($post, $data); + } + + /*-** Admin **-*/ + + function admin_set_title() { + global $post; + + if ( !$post ) + return false; + + $obj = new stdClass(); + //Section title + $sec = $this->get_section($post); + if ( $sec ) + $obj->item_section = get_the_title($sec); + //Separator + $obj->title_sep = $this->page_title_get_sep(); + $this->util->extend_client_object('posts', $obj, true); + } + + function page_title_get_sep($pad = true) { + $sep = $this->title_sep; + if ( $pad ) + $sep = ' ' . trim($sep) . ' '; + return $sep; + } + + /*-** Template **-*/ + + /** + * Builds page title for current request + * Adds subtitle to title + * Filter called by `wp_title` hook + * @param $title + * @param $sep + * @param $seplocation + * @return string Title text + */ + function page_title_get($title, $sep = '', $seplocation = '') { + global $post; + + $sep = $this->page_title_get_sep(); + + if ( is_single() ) { + //Append section name to post title + $ptitle = get_the_title(); + $ptitle_pos = ( $ptitle ) ? strpos($title, $ptitle) : false; + if ( $ptitle_pos !== false ) { + //Get section + if ( ( $sec = $this->get_section($post) ) ) { + //Append section name to post title only once + $title = substr_replace($ptitle, $ptitle . $sep . get_the_title($sec), $ptitle_pos, strlen($ptitle)) . substr($title, strlen($ptitle)); + } + } + } + + //Return new title + return $title; + } + + /** + * Builds page title for current request + * Filter called by `wp_title` hook + * @param $title + * @param $sep + * @param $seplocation + * @return string Title text + * @uses CNR::page_title_get() + */ + function page_title($title, $sep = '', $seplocation = '') { + return $this->page_title_get($title, $sep, $seplocation); + } +} \ No newline at end of file diff --git a/includes/class.posts.php b/includes/class.post_query.php similarity index 60% rename from includes/class.posts.php rename to includes/class.post_query.php index b62b93b..8d67b05 100644 --- a/includes/class.posts.php +++ b/includes/class.post_query.php @@ -1,691 +1,440 @@ - array ( - 'file' => 'js/lib.posts.js', - 'deps' => '[core]', - 'context' => 'admin' - ), - ); - - /** - * Holds posts - * @var array - */ - var $posts; - - /** - * IDs of posts in $posts - * @var array - */ - var $post_ids; - - /** - * whether or not object contains posts - * @var bool - */ - var $has; - - /** - * Index of post in current iteration - * @var int - */ - var $current; - - /** - * Total number of posts in object - * @var int - */ - var $count; - - /** - * Total number of matching posts found in DB - * All found posts may not have been returned in current query though (paging, etc.) - * @var int - */ - var $found = 0; - - /** - * Query arguments - * @var array - */ - var $args; - - /** - * Argument to be used during query to identify request - * Prefix added during init - * @see init() - * @var string - */ - var $arg_fetch = 'fetch'; - - /** - * TRUE if posts have been fetched, FALSE otherwise - * @var bool - */ - var $fetched; - - function __construct( $args = null ) { - parent::__construct(); - - parent::init(); - //Init properties - $this->init(); - - //Set arguments - if ( !empty($args) && is_array($args) ) { - $this->args = wp_parse_args($args, $this->args); - } - } - - /** - * Initializes object properties with default values - * @return void - */ - function init() { - $this->posts = array(); - $this->post_ids = array(); - $this->has = false; - $this->current = -1; - $this->count = 0; - $this->found = 0; - $this->arg_fetch = $this->add_prefix($this->arg_fetch); - $this->args = array($this->arg_fetch => true, $this->get_prefix() => true); - $this->fetched = false; - } - - /** - * Set argument value - * @param string $arg Argument name - * @param mixed $value Argument value - */ - function set_arg($arg, $value = null) { - if ( is_scalar($arg) ) { //Single argument (key/value) pair - $this->args[$arg] = $value; - } elseif ( is_array($arg) ) { //Multiple arguments - $this->args = wp_parse_args($arg, $this->args); - } - } - - /** - * Retrieve argument value - * @param string $arg Argument name - * @return mixed Argument value - */ - function get_arg($arg) { - return ( $this->arg_isset($arg) ) ? $this->args[$arg] : null; - } - - /** - * Checks if an argument is set in the object - * @param string $arg Argument name - * @return bool TRUE if argument is set, FALSE otherwise - */ - function arg_isset($arg) { - return ( isset($this->args[$arg]) ); - } - - /** - * Gets posts matching parameters and stores them in object - * - * @param int $limit (optional) Maximum number of posts to retrieve (Default: -1 = All matching posts) - * @param array $args (optional) Additional arguments to use in post query - * @return array Retrieved posts - */ - function get( $limit = null, $args = null ) { - //Global variables - global $wp_query; - - //Clear previously retrieved post data - $this->unload(); - - //Determine section - $p_arg = 'post_parent'; - if ( ! $this->arg_isset($p_arg) ) { - $parent = null; - if ( is_page() ) { - $parent = $wp_query->get_queried_object_id(); - } - if ( !! $parent ) - $this->set_arg($p_arg, $parent); - } - - //Check if parent is valid post ID - if ((int)$parent < 1) { - //Get featured posts from all sections if no valid parent is set - $parent = null; - } - - //Set query args - if ( !empty($args) ) - $this->args = wp_parse_args($args, $this->args); - //Set post limit - if ( is_numeric($limit) ) - $limit = intval($limit); - if ( ! $limit && !$this->arg_isset('numberposts') ) - $limit = ( is_feed() ) ? get_option('posts_per_rss') : get_option('posts_per_page'); - if ( $limit > 0 ) - $this->set_arg('numberposts', $limit); - - //Set offset (pagination) - if ( !is_feed() ) { - $c_page = $wp_query->get('paged'); - $offset = ( $c_page > 0 ) ? $c_page - 1 : 0; - $offset = $limit * $offset; - $this->set_arg('offset', $offset); - } - - //Retrieve posts - $filter = 'found_posts'; - $f_callback = $this->m('set_found'); - $action = 'parse_query'; - $a_callback = $this->m('set_found_flag'); - - //Add filter to populate found property during query - add_filter($filter, $f_callback); - add_action($action, $a_callback); - //Get posts - $posts =& get_posts($this->args); - //Remove filter after query has completed - remove_action($action, $a_callback); - remove_filter($filter, $f_callback); - //Save retrieved posts to array - $this->load($posts); - //Return retrieved posts so that array may be manipulated further if desired - return $this->posts; - } - - /** - * Sets object properties with post data - * - * @param array $posts Array of post objects - * @return void - */ - function load( $posts = null ) { - $this->fetched = true; - if ( !empty($posts) ) { - $this->posts = $posts; - $this->has = true; - $this->count = count($this->posts); - } - } - - /** - * Resets object properties to allow for new data to be saved - * @return void - */ - function unload() { - //Temporarily save properties that should persist - $_args = $this->args; - - //Initialize object properties - $this->init(); - - //Restore persistent properties - $this->args = $_args; - } - - /** - * Sets number of found posts in object's query - * @link `found_posts` hook - * @see WP_Query::get_posts() - * @param int $num_found - */ - function set_found($num_found) { - $this->found = $num_found; - } - - /** - * Modifies query parameters to allow `found_posts` hook to be called - * Unsets `no_found_rows` query parameter set in WP 3.1 - * @see WP_Query::parse_query() - * @link `parse_query` action hook - * @param WP_Query $q Query instance object - */ - function set_found_flag(&$q) { - if ( isset($q->query_vars[$this->arg_fetch]) ) { - $q->query_vars['no_found_rows'] = false; - } - } - - /** - * Makes sure query was run prior - * @return void - */ - function confirm_fetched() { - if ( !$this->fetched ) - $this->get(); - } - - /** - * Returns number of matching posts found in DB - * May not necessarily match number of posts contained in object (due to post limits, pagination, etc.) - * @return int Number of posts found - */ - function found() { - $this->confirm_fetched(); - return $this->found; - } - - /** - * Checks whether posts related to this object are available in the current context - * - * If no accessible posts are found, current post (section) is set as global post variable - * - * @see 'the_posts' filter - * @see get_children() - * - * @param bool $fetch Whether posts should be fetched if they have not yet been retrieved - * @return boolean TRUE if section contains children, FALSE otherwise - * Note: Will also return FALSE if section contains children, but all children have been previously accessed - * - * @global WP_Query $wp_query - * @global obj $post - */ - function has( $fetch = true ) { - global $wp_query, $post, $more; - - $this->confirm_fetched(); - - //Check if any posts on current page were retrieved - //If posts are found, make sure there are more posts - if ( $this->count > 0 && ( $this->current < $this->count - 1 ) ) { - return true; - } - - //Reset current post position if all posts have been processed - $this->rewind(); - $wp_query->in_the_loop = false; - //If no posts were found (or the last post has been previously loaded), - //load previous post back into global post variable - $i = ( $wp_query->current_post >= 0 ) ? $wp_query->current_post : 0; - if ( count($wp_query->posts) ) { - $post = $wp_query->posts[ $i ]; - setup_postdata($post); - } - - if ( is_single() || is_page() ) - $more = 1; - return false; - } - - /** - * Loads next post into global $post variable for use in the loop - * Allows use of WP template tags - * @return void - * - * @global obj $post Post object - */ - function next() { - global $post, $more, $wp_query; - - if ( $this->has() ) { - $wp_query->in_the_loop = true; - //Increment post position - $this->current++; - - //Load post into global post variable - $post = $this->posts[ $this->current ]; - - setup_postdata($post); - $more = 0; - } - } - - /** - * Resets position of current post - * Allows for multiple loops over $posts array - * @return void - */ - function rewind() { - $this->current = -1; - } - - /** - * Gets index of current post - * @return int Index position of current post - */ - function current() { - return $this->current; - } - - /** - * Returns number of posts in object - * @return int number of posts - */ - function count() { - $this->confirm_fetched(); - return $this->count; - } - - /** - * Gets the number of pages needed to list all found posts - * @return int Total number of pages - */ - function max_num_pages() { - $this->confirm_fetched(); - $posts_per_page = $this->get_arg('numberposts'); - if ( ! $posts_per_page ) - $posts_per_page = get_option('posts_per_page'); - return ceil( $this->found / $posts_per_page ); - } - - /** - * Checks if current post is the first post - * @return bool TRUE if current post is the first post in array, FALSE otherwise - */ - function is_first() { - $this->confirm_fetched(); - return ( 0 == $this->current() ); - } - - /** - * Checks if current featured post is the last item in the post array - * @return bool TRUE if item is the last featured item, FALSE otherwise - */ - function is_last() { - $this->confirm_fetched(); - return ($this->current == $this->count - 1) ? true : false; - } - - /** - * @param int $post (optional) ID of post to check for existence in the object's posts array (uses global $post object if no value passed) - * @return bool TRUE if post is in posts array - */ - function contains( $post = null ) { - $this->confirm_fetched(); - //Use argument value if it is an integer - if ( is_numeric($post) && intval($post) > 0 ) { - //Cast to object and set ID property (for later use) - $post = (object) $post; - $post->ID = $post->scalar; - } - //Otherwise check if argument is valid post - elseif ( !$this->util->check_post($post) ) { - return false; - } - - //Check for existence of post ID in posts array - return in_array($post->ID, $this->get_ids()); - } - - /** - * Retrieve IDs of all retrieved posts - */ - function get_ids() { - $this->confirm_fetched(); - - if ( $this->has && empty($this->post_ids) ) { - //Build array of post ids in array - foreach ($this->posts as $post) { - $this->post_ids[] = $post->ID; - } - } - - return $this->post_ids; - } - -} - -/** - * @package Cornerstone - * @subpackage Posts - * @author Archetyped - * - */ -class CNR_Post extends CNR_Base { - - /*-** Properties **-*/ - - /** - * Script files - * @see CNR_Base::client_files - * @var array - */ - var $scripts = array ( - 'posts' => array ( - 'file' => 'js/lib.posts.js', - 'deps' => '[core]', - 'context' => 'admin' - ), - ); - - /** - * Default title separator - * @var string - */ - var $title_sep = '‹'; - - /*-** Initialization **-*/ - - function register_hooks() { - parent::register_hooks(); - - //Template - // add_filter('wp_title', $this->m('page_title'), 11, 3); - - //Admin - add_action('admin_head', $this->m('admin_set_title'), 11); - } - - /** - * Gets entire parent tree of post as an array - * - * Array order is from top level to immediate post parent - * @static - * @param object $post Post to get path for - * @param string $prop Property to retrieve from parents. If specified, array will contain only this property from parents - * @param $depth Unused - * @return array of Post Objects/Properties - */ - static function get_parents($post, $prop = '', $depth = '') { - $parents = get_post_ancestors($post = get_post($post, OBJECT, '')); - if ( is_object($post) && !empty($parents) && ('id' != strtolower(trim($prop))) ) { - //Retrieve post data for parents if full data or property other than post ID is required - $args = array( - 'include' => $parents, - 'post_type' => 'any', - ); - $ancestors = get_posts($args); - - //Sort array in ancestor order - $temp_parents = array(); - foreach ($ancestors as $ancestor) { - //Get index of ancestor - $i = array_search($ancestor->ID, $parents); - if ( false === $i ) - continue; - //Insert post at index - $temp_parents[$i] = $ancestor; - } - - if ( !empty($temp_parents) ) - $parents = $temp_parents; - } - //Reverse Array (to put top level parent at beginning of array) - $parents = array_reverse($parents); - return $parents; - } - - /** - * Get the IDs of a collection of posts - * @return array IDs of Posts passed to function - * @param array $posts Array of Post objects - */ - function get_ids($posts) { - $callback = create_function('$post', 'return $post->ID;'); - $arr_ids = array_map($callback, $posts); - return $arr_ids; - } - - /*-** Children **-*/ - - /** - * Gets children posts of specified page and stores them for later use - * This method hooks into 'the_posts' filter to retrieve child posts for any single page retrieved by WP - * @param int|object $post ID or Object of Post to get children for - * @return CNR_Post_Query $posts Posts array (required by 'the_posts' filter) - * - * @global WP_Query $wp_query - */ - function &get_children($post = null) { - //Global variables - global $wp_query; - $children = new CNR_Post_Query(); - if ( empty($post) && !empty($wp_query->posts) ) - $post = $wp_query->posts[0]; - - if ( is_object($post) ) - $post = $post->ID; - if ( is_numeric($post) ) - $post = (int) $post; - else - return $children; - - //Get children posts of page - if ( $post ) { - //Set arguments to retrieve children posts of current page - $limit = ( is_feed() ) ? get_option('posts_per_rss') : get_option('posts_per_page'); - $offset = ( is_paged() ) ? ( (get_query_var('paged') - 1) * $limit ) : 0; - $c_args = array( - 'post_parent' => $post, - 'numberposts' => $limit, - 'offset' => $offset - ); - - //Create post query object - $children->set_arg($c_args); - - //Get children posts - $children->get(); - } - - return $children; - } - - /*-** Post Metadata **-*/ - - /** - * Retrieves the post's section data - * @param string $data (optional) Section data to return (Default: full section object) - * Possible values: - * NULL Full section post object - * Column name Post column data (if exists) - * - * @return mixed post's section data (Default: ID value) - */ - function get_section($post = null, $data = null) { - $p = get_post($post); - $retval = 0; - if ( is_object($p) && isset($p->post_parent) ) - $retval = intval($p->post_parent); - - //Get specified section data for posts with valid parents - if ( $retval > 0 ) { - if ( !empty($data) ) { - $retval = get_post_field($data, $retval); - } else { - $retval = get_post($retval); - } - } - - return $retval; - } - - /** - * Prints the post's section data - * @uses CNR_Post::get_section() - * @param string $type (optional) Type of data to return (Default: ID) - */ - function the_section($post = null, $data = 'ID') { - if ( empty($data) ) - $data = 'ID'; - echo CNR_Post::get_section($post, $data); - } - - /*-** Admin **-*/ - - function admin_set_title() { - global $post; - - if ( !$post ) - return false; - - $obj = new stdClass(); - //Section title - $sec = $this->get_section($post); - if ( $sec ) - $obj->item_section = get_the_title($sec); - //Separator - $obj->title_sep = $this->page_title_get_sep(); - $this->util->extend_client_object('posts', $obj, true); - } - - function page_title_get_sep($pad = true) { - $sep = $this->title_sep; - if ( $pad ) - $sep = ' ' . trim($sep) . ' '; - return $sep; - } - - /*-** Template **-*/ - - /** - * Builds page title for current request - * Adds subtitle to title - * Filter called by `wp_title` hook - * @param $title - * @param $sep - * @param $seplocation - * @return string Title text - */ - function page_title_get($title, $sep = '', $seplocation = '') { - global $post; - - $sep = $this->page_title_get_sep(); - - if ( is_single() ) { - //Append section name to post title - $ptitle = get_the_title(); - $ptitle_pos = ( $ptitle ) ? strpos($title, $ptitle) : false; - if ( $ptitle_pos !== false ) { - //Get section - if ( ( $sec = $this->get_section($post) ) ) { - //Append section name to post title only once - $title = substr_replace($ptitle, $ptitle . $sep . get_the_title($sec), $ptitle_pos, strlen($ptitle)) . substr($title, strlen($ptitle)); - } - } - } - - //Return new title - return $title; - } - - /** - * Builds page title for current request - * Filter called by `wp_title` hook - * @param $title - * @param $sep - * @param $seplocation - * @return string Title text - * @uses CNR::page_title_get() - */ - function page_title($title, $sep = '', $seplocation = '') { - return $this->page_title_get($title, $sep, $seplocation); - } -} - -?> \ No newline at end of file + array ( + 'file' => 'js/lib.posts.js', + 'deps' => '[core]', + 'context' => 'admin' + ), + ); + + /** + * Holds posts + * @var array + */ + var $posts; + + /** + * IDs of posts in $posts + * @var array + */ + var $post_ids; + + /** + * whether or not object contains posts + * @var bool + */ + var $has; + + /** + * Index of post in current iteration + * @var int + */ + var $current; + + /** + * Total number of posts in object + * @var int + */ + var $count; + + /** + * Total number of matching posts found in DB + * All found posts may not have been returned in current query though (paging, etc.) + * @var int + */ + var $found = 0; + + /** + * Query arguments + * @var array + */ + var $args; + + /** + * Argument to be used during query to identify request + * Prefix added during init + * @see init() + * @var string + */ + var $arg_fetch = 'fetch'; + + /** + * TRUE if posts have been fetched, FALSE otherwise + * @var bool + */ + var $fetched; + + function __construct( $args = null ) { + parent::__construct(); + + parent::init(); + //Init properties + $this->init(); + + //Set arguments + if ( !empty($args) && is_array($args) ) { + $this->args = wp_parse_args($args, $this->args); + } + } + + /** + * Initializes object properties with default values + * @return void + */ + function init() { + $this->posts = array(); + $this->post_ids = array(); + $this->has = false; + $this->current = -1; + $this->count = 0; + $this->found = 0; + $this->arg_fetch = $this->add_prefix($this->arg_fetch); + $this->args = array($this->arg_fetch => true, $this->get_prefix() => true); + $this->fetched = false; + } + + /** + * Set argument value + * @param string $arg Argument name + * @param mixed $value Argument value + */ + function set_arg($arg, $value = null) { + if ( is_scalar($arg) ) { //Single argument (key/value) pair + $this->args[$arg] = $value; + } elseif ( is_array($arg) ) { //Multiple arguments + $this->args = wp_parse_args($arg, $this->args); + } + } + + /** + * Retrieve argument value + * @param string $arg Argument name + * @return mixed Argument value + */ + function get_arg($arg) { + return ( $this->arg_isset($arg) ) ? $this->args[$arg] : null; + } + + /** + * Checks if an argument is set in the object + * @param string $arg Argument name + * @return bool TRUE if argument is set, FALSE otherwise + */ + function arg_isset($arg) { + return ( isset($this->args[$arg]) ); + } + + /** + * Gets posts matching parameters and stores them in object + * + * @param int $limit (optional) Maximum number of posts to retrieve (Default: -1 = All matching posts) + * @param array $args (optional) Additional arguments to use in post query + * @return array Retrieved posts + */ + function get( $limit = null, $args = null ) { + //Global variables + global $wp_query; + + //Clear previously retrieved post data + $this->unload(); + + //Determine section + $p_arg = 'post_parent'; + if ( ! $this->arg_isset($p_arg) ) { + $parent = null; + if ( is_page() ) { + $parent = $wp_query->get_queried_object_id(); + } + if ( !! $parent ) + $this->set_arg($p_arg, $parent); + } + + //Check if parent is valid post ID + if ((int)$parent < 1) { + //Get featured posts from all sections if no valid parent is set + $parent = null; + } + + //Set query args + if ( !empty($args) ) + $this->args = wp_parse_args($args, $this->args); + //Set post limit + if ( is_numeric($limit) ) + $limit = intval($limit); + if ( ! $limit && !$this->arg_isset('numberposts') ) + $limit = ( is_feed() ) ? get_option('posts_per_rss') : get_option('posts_per_page'); + if ( $limit > 0 ) + $this->set_arg('numberposts', $limit); + + //Set offset (pagination) + if ( !is_feed() ) { + $c_page = $wp_query->get('paged'); + $offset = ( $c_page > 0 ) ? $c_page - 1 : 0; + $offset = $limit * $offset; + $this->set_arg('offset', $offset); + } + + //Retrieve posts + $filter = 'found_posts'; + $f_callback = $this->m('set_found'); + $action = 'parse_query'; + $a_callback = $this->m('set_found_flag'); + + //Add filter to populate found property during query + add_filter($filter, $f_callback); + add_action($action, $a_callback); + //Get posts + $posts =& get_posts($this->args); + //Remove filter after query has completed + remove_action($action, $a_callback); + remove_filter($filter, $f_callback); + //Save retrieved posts to array + $this->load($posts); + //Return retrieved posts so that array may be manipulated further if desired + return $this->posts; + } + + /** + * Sets object properties with post data + * + * @param array $posts Array of post objects + * @return void + */ + function load( $posts = null ) { + $this->fetched = true; + if ( !empty($posts) ) { + $this->posts = $posts; + $this->has = true; + $this->count = count($this->posts); + } + } + + /** + * Resets object properties to allow for new data to be saved + * @return void + */ + function unload() { + //Temporarily save properties that should persist + $_args = $this->args; + + //Initialize object properties + $this->init(); + + //Restore persistent properties + $this->args = $_args; + } + + /** + * Sets number of found posts in object's query + * @link `found_posts` hook + * @see WP_Query::get_posts() + * @param int $num_found + */ + function set_found($num_found) { + $this->found = $num_found; + } + + /** + * Modifies query parameters to allow `found_posts` hook to be called + * Unsets `no_found_rows` query parameter set in WP 3.1 + * @see WP_Query::parse_query() + * @link `parse_query` action hook + * @param WP_Query $q Query instance object + */ + function set_found_flag(&$q) { + if ( isset($q->query_vars[$this->arg_fetch]) ) { + $q->query_vars['no_found_rows'] = false; + } + } + + /** + * Makes sure query was run prior + * @return void + */ + function confirm_fetched() { + if ( !$this->fetched ) + $this->get(); + } + + /** + * Returns number of matching posts found in DB + * May not necessarily match number of posts contained in object (due to post limits, pagination, etc.) + * @return int Number of posts found + */ + function found() { + $this->confirm_fetched(); + return $this->found; + } + + /** + * Checks whether posts related to this object are available in the current context + * + * If no accessible posts are found, current post (section) is set as global post variable + * + * @see 'the_posts' filter + * @see get_children() + * + * @param bool $fetch Whether posts should be fetched if they have not yet been retrieved + * @return boolean TRUE if section contains children, FALSE otherwise + * Note: Will also return FALSE if section contains children, but all children have been previously accessed + * + * @global WP_Query $wp_query + * @global obj $post + */ + function has( $fetch = true ) { + global $wp_query, $post, $more; + + $this->confirm_fetched(); + + //Check if any posts on current page were retrieved + //If posts are found, make sure there are more posts + if ( $this->count > 0 && ( $this->current < $this->count - 1 ) ) { + return true; + } + + //Reset current post position if all posts have been processed + $this->rewind(); + $wp_query->in_the_loop = false; + //If no posts were found (or the last post has been previously loaded), + //load previous post back into global post variable + $i = ( $wp_query->current_post >= 0 ) ? $wp_query->current_post : 0; + if ( count($wp_query->posts) ) { + $post = $wp_query->posts[ $i ]; + setup_postdata($post); + } + + if ( is_single() || is_page() ) + $more = 1; + return false; + } + + /** + * Loads next post into global $post variable for use in the loop + * Allows use of WP template tags + * @return void + * + * @global obj $post Post object + */ + function next() { + global $post, $more, $wp_query; + + if ( $this->has() ) { + $wp_query->in_the_loop = true; + //Increment post position + $this->current++; + + //Load post into global post variable + $post = $this->posts[ $this->current ]; + + setup_postdata($post); + $more = 0; + } + } + + /** + * Resets position of current post + * Allows for multiple loops over $posts array + * @return void + */ + function rewind() { + $this->current = -1; + } + + /** + * Gets index of current post + * @return int Index position of current post + */ + function current() { + return $this->current; + } + + /** + * Returns number of posts in object + * @return int number of posts + */ + function count() { + $this->confirm_fetched(); + return $this->count; + } + + /** + * Gets the number of pages needed to list all found posts + * @return int Total number of pages + */ + function max_num_pages() { + $this->confirm_fetched(); + $posts_per_page = $this->get_arg('numberposts'); + if ( ! $posts_per_page ) + $posts_per_page = get_option('posts_per_page'); + return ceil( $this->found / $posts_per_page ); + } + + /** + * Checks if current post is the first post + * @return bool TRUE if current post is the first post in array, FALSE otherwise + */ + function is_first() { + $this->confirm_fetched(); + return ( 0 == $this->current() ); + } + + /** + * Checks if current featured post is the last item in the post array + * @return bool TRUE if item is the last featured item, FALSE otherwise + */ + function is_last() { + $this->confirm_fetched(); + return ($this->current == $this->count - 1) ? true : false; + } + + /** + * @param int $post (optional) ID of post to check for existence in the object's posts array (uses global $post object if no value passed) + * @return bool TRUE if post is in posts array + */ + function contains( $post = null ) { + $this->confirm_fetched(); + //Use argument value if it is an integer + if ( is_numeric($post) && intval($post) > 0 ) { + //Cast to object and set ID property (for later use) + $post = (object) $post; + $post->ID = $post->scalar; + } + //Otherwise check if argument is valid post + elseif ( !$this->util->check_post($post) ) { + return false; + } + + //Check for existence of post ID in posts array + return in_array($post->ID, $this->get_ids()); + } + + /** + * Retrieve IDs of all retrieved posts + */ + function get_ids() { + $this->confirm_fetched(); + + if ( $this->has && empty($this->post_ids) ) { + //Build array of post ids in array + foreach ($this->posts as $post) { + $this->post_ids[] = $post->ID; + } + } + + return $this->post_ids; + } + +} \ No newline at end of file From 09d2bc8899a552d90a198906969d32e65950e1ee Mon Sep 17 00:00:00 2001 From: SM Date: Thu, 16 Jul 2015 09:14:54 -1000 Subject: [PATCH 14/24] Refactor: Move Content Type initialization to `load.php` --- includes/class.content-types.php | 27 --------------------------- load.php | 25 ++++++++++++++++++++++++- 2 files changed, 24 insertions(+), 28 deletions(-) delete mode 100644 includes/class.content-types.php diff --git a/includes/class.content-types.php b/includes/class.content-types.php deleted file mode 100644 index 0f4f278..0000000 --- a/includes/class.content-types.php +++ /dev/null @@ -1,27 +0,0 @@ -init(); - -/* Hooks */ - -//Default placeholder handlers -cnr_register_placeholder_handler('all', array('CNR_Field_Type', 'process_placeholder_default'), 11); -cnr_register_placeholder_handler('field_id', array('CNR_Field_Type', 'process_placeholder_id')); -cnr_register_placeholder_handler('field_name', array('CNR_Field_Type', 'process_placeholder_name')); -cnr_register_placeholder_handler('data', array('CNR_Field_Type', 'process_placeholder_data')); -cnr_register_placeholder_handler('loop', array('CNR_Field_Type', 'process_placeholder_loop')); -cnr_register_placeholder_handler('data_ext', array('CNR_Field_Type', 'process_placeholder_data_ext')); -cnr_register_placeholder_handler('rich_editor', array('CNR_Field_Type', 'process_placeholder_rich_editor')); - - -?> diff --git a/load.php b/load.php index 28fe0bc..dd139ee 100644 --- a/load.php +++ b/load.php @@ -36,4 +36,27 @@ function cnr_autoload($classname) { $path = dirname(__FILE__) . '/'; require_once $path . 'controller.php'; $GLOBALS['cnr'] = new Cornerstone(); -require_once $path . 'functions.php'; \ No newline at end of file +require_once $path . 'functions.php'; + +/* Variables */ + +//Global content type variables +if ( !isset($cnr_content_types) ) + $cnr_content_types = array(); +if ( !isset($cnr_field_types) ) + $cnr_field_types = array(); + +/* Init */ +$cnr_content_utilities = new CNR_Content_Utilities(); +$cnr_content_utilities->init(); + +/* Hooks */ + +// Register Default placeholder handlers +cnr_register_placeholder_handler('all', array('CNR_Field_Type', 'process_placeholder_default'), 11); +cnr_register_placeholder_handler('field_id', array('CNR_Field_Type', 'process_placeholder_id')); +cnr_register_placeholder_handler('field_name', array('CNR_Field_Type', 'process_placeholder_name')); +cnr_register_placeholder_handler('data', array('CNR_Field_Type', 'process_placeholder_data')); +cnr_register_placeholder_handler('loop', array('CNR_Field_Type', 'process_placeholder_loop')); +cnr_register_placeholder_handler('data_ext', array('CNR_Field_Type', 'process_placeholder_data_ext')); +cnr_register_placeholder_handler('rich_editor', array('CNR_Field_Type', 'process_placeholder_rich_editor')); From fcf57574f46f4cd3441b0d1fc0d7c588974a9e43 Mon Sep 17 00:00:00 2001 From: SM Date: Thu, 16 Jul 2015 09:31:41 -1000 Subject: [PATCH 15/24] Update: Enable class autoloader --- load.php | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/load.php b/load.php index dd139ee..594c301 100644 --- a/load.php +++ b/load.php @@ -30,25 +30,24 @@ function cnr_autoload($classname) { } // Register autoloader -//spl_autoload_register('cnr_autoload'); +spl_autoload_register('cnr_autoload'); /* Load Assets */ $path = dirname(__FILE__) . '/'; require_once $path . 'controller.php'; -$GLOBALS['cnr'] = new Cornerstone(); require_once $path . 'functions.php'; /* Variables */ //Global content type variables -if ( !isset($cnr_content_types) ) - $cnr_content_types = array(); -if ( !isset($cnr_field_types) ) - $cnr_field_types = array(); +if ( !isset($GLOBALS['cnr_content_types']) ) + $GLOBALS['cnr_content_types'] = array(); +if ( !isset($GLOBALS['cnr_field_types']) ) + $GLOBALS['cnr_field_types'] = array(); /* Init */ -$cnr_content_utilities = new CNR_Content_Utilities(); -$cnr_content_utilities->init(); +$GLOBALS['cnr_content_utilities'] = new CNR_Content_Utilities(); +$GLOBALS['cnr_content_utilities']->init(); /* Hooks */ @@ -60,3 +59,7 @@ function cnr_autoload($classname) { cnr_register_placeholder_handler('loop', array('CNR_Field_Type', 'process_placeholder_loop')); cnr_register_placeholder_handler('data_ext', array('CNR_Field_Type', 'process_placeholder_data_ext')); cnr_register_placeholder_handler('rich_editor', array('CNR_Field_Type', 'process_placeholder_rich_editor')); + +/* Start */ + +$GLOBALS['cnr'] = new Cornerstone(); \ No newline at end of file From 85143d6e7eea5654d26d6a398c05ad18bbb25fe2 Mon Sep 17 00:00:00 2001 From: SM Date: Thu, 16 Jul 2015 09:43:00 -1000 Subject: [PATCH 16/24] Media/Optimize: `is_custom_media()` should ensure `HTTP_REFERER` variable exists before processing --- includes/class.media.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/includes/class.media.php b/includes/class.media.php index c6b6295..55c5001 100644 --- a/includes/class.media.php +++ b/includes/class.media.php @@ -291,7 +291,7 @@ function is_custom_media() { $upload = false; if (isset($_REQUEST[$action])) $ret = true; - else { + elseif (isset($_SERVER['HTTP_REFERER']) ) { $qs = array(); $ref = parse_url($_SERVER['HTTP_REFERER']); if ( isset($ref['query']) ) From b2c8be8cab013dc9ab419880f478b2ef653f55bd Mon Sep 17 00:00:00 2001 From: SM Date: Tue, 21 Jul 2015 09:39:59 -1000 Subject: [PATCH 17/24] Optimize: Static method labels and calls * Add `static` keyword to static methods * Remove static calls to non-static methods --- includes/class.content_base.php | 2 +- includes/class.content_type.php | 18 ++++++++++++------ includes/class.feeds.php | 2 +- includes/class.field_type.php | 14 +++++++------- includes/class.post.php | 4 ++-- includes/class.utilities.php | 2 +- 6 files changed, 24 insertions(+), 18 deletions(-) diff --git a/includes/class.content_base.php b/includes/class.content_base.php index f64165d..064e7c7 100644 --- a/includes/class.content_base.php +++ b/includes/class.content_base.php @@ -308,7 +308,7 @@ function set_data($value, $name = '') { * Retrieve base_class property * @return string base_class property of current class/instance object */ - function get_base_class() { + static function get_base_class() { $ret = ''; if ( isset($this) ) $ret = $this->base_class; diff --git a/includes/class.content_type.php b/includes/class.content_type.php index 65059d7..a38bd98 100644 --- a/includes/class.content_type.php +++ b/includes/class.content_type.php @@ -140,8 +140,10 @@ function &add_field($id, $parent, $properties = array(), $group = null) { * @param string|CNR_Field $field Object or Field ID to remove */ function remove_field($field) { - $field = CNR_Field_Type::get_id($field); - if ( !$field ) + if ( $field instanceof CNR_Field_Type ) { + $field = $field->get_id(); + } + if ( !is_string($field) || empty($field) ) return false; //Remove from fields array @@ -224,8 +226,10 @@ function add_to_group($group, $fields) { */ function remove_from_group($field, $group = '') { //Get ID of field to remove or stop execution if field invalid - $field = CNR_Field_Type::get_id($field); - if ( !$field ) + if ( $field instanceof CNR_Field_Type ) { + $field = $field->get_id(); + } + if ( !is_string($field) || empty($field) ) return false; //Remove field from group @@ -328,8 +332,10 @@ function set_data($field, $value = '') { if ( 1 == func_num_args() && is_array($field) ) $this->data = $field; else { - $field = CNR_Field_Type::get_id($field); - if ( empty($field) ) + if ( $field instanceof CNR_Field_Type ) { + $field = $field->get_id(); + } + if ( !is_string($field) || empty($field) ) return false; $this->data[$field] = $value; } diff --git a/includes/class.feeds.php b/includes/class.feeds.php index 477b5e4..255cdb4 100644 --- a/includes/class.feeds.php +++ b/includes/class.feeds.php @@ -82,7 +82,7 @@ function feed_redirect() { /** * Retrieves a section's child content for output in a feed */ - function get_children() { + static function get_children() { if ( is_page() && is_feed() ) { global $wp_query; //Get children of current page diff --git a/includes/class.field_type.php b/includes/class.field_type.php index 8809bc5..de4df98 100644 --- a/includes/class.field_type.php +++ b/includes/class.field_type.php @@ -527,7 +527,7 @@ static function register_placeholder_handler($placeholder, $handler, $priority = * @param array $data Extended data for field * @return string Value to use in place of current placeholder */ - function process_placeholder_default($ph_output, $field, $placeholder, $layout, $data) { + static function process_placeholder_default($ph_output, $field, $placeholder, $layout, $data) { //Validate parameters before processing if ( empty($ph_output) && is_a($field, 'CNR_Field_Type') && is_array($placeholder) ) { //Build path to replacement data @@ -572,7 +572,7 @@ function process_placeholder_default($ph_output, $field, $placeholder, $layout, * @see CNR_Field_Type::process_placeholder_default for parameter descriptions * @return string Placeholder output */ - function process_placeholder_id($ph_output, $field, $placeholder, $layout, $data) { + static function process_placeholder_id($ph_output, $field, $placeholder, $layout, $data) { //Get attributes $args = wp_parse_args($placeholder['attributes'], array('format' => 'attr_id')); return $field->get_id($args); @@ -584,7 +584,7 @@ function process_placeholder_id($ph_output, $field, $placeholder, $layout, $data * @see CNR_Field_Type::process_placeholder_default for parameter descriptions * @return string Placeholder output */ - function process_placeholder_name($ph_output, $field, $placeholder, $layout, $data) { + static function process_placeholder_name($ph_output, $field, $placeholder, $layout, $data) { //Get attributes $args = wp_parse_args($placeholder['attributes'], array('format' => 'default')); return $field->get_id($args); @@ -595,7 +595,7 @@ function process_placeholder_name($ph_output, $field, $placeholder, $layout, $da * @see CNR_Field_Type::process_placeholder_default for parameter descriptions * @return string Placeholder output */ - function process_placeholder_data($ph_output, $field, $placeholder, $layout) { + static function process_placeholder_data($ph_output, $field, $placeholder, $layout) { $val = $field->get_data(); if ( !is_null($val) ) { $ph_output = $val; @@ -620,7 +620,7 @@ function process_placeholder_data($ph_output, $field, $placeholder, $layout) { * @see CNR_Field_Type::process_placeholder_default for parameter descriptions * @return string Placeholder output */ - function process_placeholder_loop($ph_output, $field, $placeholder, $layout, $data) { + static function process_placeholder_loop($ph_output, $field, $placeholder, $layout, $data) { //Setup loop options $attr_defaults = array ( 'layout' => '', @@ -668,7 +668,7 @@ function process_placeholder_loop($ph_output, $field, $placeholder, $layout, $da * @see CNR_Field_Type::process_placeholder_default for parameter descriptions * @return string Placeholder output */ - function process_placeholder_data_ext($ph_output, $field, $placeholder, $layout, $data) { + static function process_placeholder_data_ext($ph_output, $field, $placeholder, $layout, $data) { if ( isset($placeholder['attributes']['id']) && ($key = $placeholder['attributes']['id']) && isset($data[$key]) ) { $ph_output = strval($data[$key]); } @@ -681,7 +681,7 @@ function process_placeholder_data_ext($ph_output, $field, $placeholder, $layout, * @see CNR_Field_Type::process_placeholder_default for parameter descriptions * @return string Placeholder output */ - function process_placeholder_rich_editor($ph_output, $field, $placeholder, $layout, $data) { + static function process_placeholder_rich_editor($ph_output, $field, $placeholder, $layout, $data) { $id = $field->get_id( array ( 'format' => 'attr_id' )); diff --git a/includes/class.post.php b/includes/class.post.php index e30ee77..f76eec5 100644 --- a/includes/class.post.php +++ b/includes/class.post.php @@ -146,7 +146,7 @@ function &get_children($post = null) { * * @return mixed post's section data (Default: ID value) */ - function get_section($post = null, $data = null) { + static function get_section($post = null, $data = null) { $p = get_post($post); $retval = 0; if ( is_object($p) && isset($p->post_parent) ) @@ -169,7 +169,7 @@ function get_section($post = null, $data = null) { * @uses CNR_Post::get_section() * @param string $type (optional) Type of data to return (Default: ID) */ - function the_section($post = null, $data = 'ID') { + static function the_section($post = null, $data = 'ID') { if ( empty($data) ) $data = 'ID'; echo CNR_Post::get_section($post, $data); diff --git a/includes/class.utilities.php b/includes/class.utilities.php index bfbdf85..a8a05ff 100644 --- a/includes/class.utilities.php +++ b/includes/class.utilities.php @@ -918,7 +918,7 @@ function property_exists($class, $property) { * @param string $property Property name to retrieve * @return mixed Property value */ - function &get_property(&$obj, $property) { + static function &get_property($obj, $property) { $property = trim($property); //Object if ( is_object($obj) ) From fb0c57f933a8bc1a7065378686fbe6cf868378c0 Mon Sep 17 00:00:00 2001 From: SM Date: Tue, 21 Jul 2015 09:47:12 -1000 Subject: [PATCH 18/24] Optimize: Variable declarations (assignment by reference) --- includes/class.post_query.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/includes/class.post_query.php b/includes/class.post_query.php index 8d67b05..35c7f94 100644 --- a/includes/class.post_query.php +++ b/includes/class.post_query.php @@ -197,7 +197,7 @@ function get( $limit = null, $args = null ) { add_filter($filter, $f_callback); add_action($action, $a_callback); //Get posts - $posts =& get_posts($this->args); + $posts = get_posts($this->args); //Remove filter after query has completed remove_action($action, $a_callback); remove_filter($filter, $f_callback); From 367c3516c43c1795801c5077df1c484810cffce0 Mon Sep 17 00:00:00 2001 From: SM Date: Tue, 21 Jul 2015 09:54:57 -1000 Subject: [PATCH 19/24] Optimize: Remove closing PHP bracket --- controller.php | 2 -- includes/class.base.php | 4 +--- includes/class.content_type.php | 1 - includes/class.feeds.php | 3 +-- includes/class.field_type.php | 1 - includes/class.media.php | 3 +-- includes/class.post_query.php | 1 - includes/class.structure.php | 3 +-- 8 files changed, 4 insertions(+), 14 deletions(-) diff --git a/controller.php b/controller.php index d90da5c..9d2a530 100644 --- a/controller.php +++ b/controller.php @@ -233,5 +233,3 @@ function post_has_content($post = null) { return false; } } - -?> diff --git a/includes/class.base.php b/includes/class.base.php index f840eeb..93b2ce5 100644 --- a/includes/class.base.php +++ b/includes/class.base.php @@ -356,6 +356,4 @@ function remove_prefix($text, $sep = null) { $args = func_get_args(); return call_user_func_array($this->util->m($this->util, 'remove_prefix'), $args); } -} - -?> \ No newline at end of file +} \ No newline at end of file diff --git a/includes/class.content_type.php b/includes/class.content_type.php index a38bd98..80a9837 100644 --- a/includes/class.content_type.php +++ b/includes/class.content_type.php @@ -399,5 +399,4 @@ function admin_build_meta_box($post, $box) { function get_meta_value() { return serialize(array($this->id)); } - } \ No newline at end of file diff --git a/includes/class.feeds.php b/includes/class.feeds.php index 255cdb4..74f8355 100644 --- a/includes/class.feeds.php +++ b/includes/class.feeds.php @@ -211,5 +211,4 @@ function get_links() { function the_links() { echo $this->get_links(); } -} -?> \ No newline at end of file +} \ No newline at end of file diff --git a/includes/class.field_type.php b/includes/class.field_type.php index de4df98..af55b46 100644 --- a/includes/class.field_type.php +++ b/includes/class.field_type.php @@ -695,5 +695,4 @@ static function process_placeholder_rich_editor($ph_output, $field, $placeholder $out = ob_get_clean(); return $out; } - } \ No newline at end of file diff --git a/includes/class.media.php b/includes/class.media.php index 55c5001..5c632f3 100644 --- a/includes/class.media.php +++ b/includes/class.media.php @@ -627,5 +627,4 @@ function type_admin_page() { \ No newline at end of file +} \ No newline at end of file diff --git a/includes/class.post_query.php b/includes/class.post_query.php index 35c7f94..667fd14 100644 --- a/includes/class.post_query.php +++ b/includes/class.post_query.php @@ -436,5 +436,4 @@ function get_ids() { return $this->post_ids; } - } \ No newline at end of file diff --git a/includes/class.structure.php b/includes/class.structure.php index 7c43ca1..cca955e 100644 --- a/includes/class.structure.php +++ b/includes/class.structure.php @@ -812,5 +812,4 @@ function stop_processing() { function is_processing() { return !!$this->status_processing; } -} -?> \ No newline at end of file +} \ No newline at end of file From 48a1ae68ead74f79fc2038bd88c0911ceeca5c0a Mon Sep 17 00:00:00 2001 From: SM Date: Wed, 22 Jul 2015 09:07:49 -1000 Subject: [PATCH 20/24] SYS/Add: Grunt workflow --- Gruntfile.js | 49 +++++++++++++++++++++++++++++++++++++++++ grunt/jshint.js | 38 ++++++++++++++++++++++++++++++++ grunt/phplint.js | 14 ++++++++++++ grunt/sass.js | 35 +++++++++++++++++++++++++++++ grunt/uglify.js | 21 ++++++++++++++++++ grunt/watch.js | 57 ++++++++++++++++++++++++++++++++++++++++++++++++ package.json | 20 +++++++++++++++++ 7 files changed, 234 insertions(+) create mode 100644 Gruntfile.js create mode 100644 grunt/jshint.js create mode 100644 grunt/phplint.js create mode 100644 grunt/sass.js create mode 100644 grunt/uglify.js create mode 100644 grunt/watch.js create mode 100644 package.json diff --git a/Gruntfile.js b/Gruntfile.js new file mode 100644 index 0000000..0d387f6 --- /dev/null +++ b/Gruntfile.js @@ -0,0 +1,49 @@ +module.exports = function(grunt) { + // Load tasks + require('load-grunt-tasks')(grunt); + // Display task timing + require('time-grunt')(grunt); + // Project configuration. + grunt.initConfig({ + // Metadata + pkg : grunt.file.readJSON('package.json'), + // Variables + paths : { + // Base dir assets dir + base : 'client', + + // PHP assets + php : { + files_std : ['*.php', '**/*.php', '!node_modules/**/*.php'], // Standard file match + files : '<%= paths.php.files_std %>' // Dynamic file match + }, + + // JavaScript assets + js : { + base : 'js', // Base dir + src : '<%= paths.js.base %>/dev', // Development code + dest : '<%= paths.js.base %>/prod', // Production code + files_std : '**/<%= paths.js.src %>/**/*.js', // Standard file match + files : '<%= paths.js.files_std %>' // Dynamic file match + }, + + // Sass assets + sass : { + src : 'sass', // Source files dir + dest : 'css', // Compiled files dir + ext : '.css', // Compiled extension + target : '*.scss', // Only Sass files in CWD + exclude : '!_*.scss', // Do not process partials + base_src : '<%= paths.base %>/<%= paths.sass.src %>', // Base source dir + base_dest : '<%= paths.base %>/<%= paths.sass.dest %>', // Base compile dir + } + }, + }); + + // Load task configurations + grunt.loadTasks('grunt'); + + // Default Tasks + grunt.registerTask('build', ['phplint', 'jshint:all', 'uglify', 'sass']); + grunt.registerTask('watch_all', ['watch:js', 'watch:sass']); +}; \ No newline at end of file diff --git a/grunt/jshint.js b/grunt/jshint.js new file mode 100644 index 0000000..8adbfb7 --- /dev/null +++ b/grunt/jshint.js @@ -0,0 +1,38 @@ +module.exports = function(grunt) { + +grunt.config('jshint', { + options : { + reporter: require('jshint-stylish'), + curly : true, + eqeqeq : true, + immed : true, + latedef : true, + newcap : false, + noarg : true, + sub : true, + undef : true, + unused : true, + boss : true, + eqnull : true, + browser : true, + jquery : true, + globals : {} + }, + grunt : { + options : { + node : true + }, + src : ['Gruntfile.js', 'grunt/*.js'] + }, + all : { + options : { + globals : { + 'SLB' : true, + 'console' : true + } + }, + src : ['<%= paths.js.files %>'] + }, +}); + +}; \ No newline at end of file diff --git a/grunt/phplint.js b/grunt/phplint.js new file mode 100644 index 0000000..d1f095a --- /dev/null +++ b/grunt/phplint.js @@ -0,0 +1,14 @@ +module.exports = function(grunt) { + +grunt.config('phplint', { + options : { + phpArgs : { + '-lf': null + } + }, + all : { + src : '<%= paths.php.files %>' + } +}); + +}; \ No newline at end of file diff --git a/grunt/sass.js b/grunt/sass.js new file mode 100644 index 0000000..2278eb7 --- /dev/null +++ b/grunt/sass.js @@ -0,0 +1,35 @@ +module.exports = function(grunt) { + +grunt.config('sass', { + options : { + outputStyle : 'compressed', + }, + core : { + files : [{ + expand : true, + cwd : '<%= paths.sass.base_src %>/', + dest : '<%= paths.sass.base_dest %>/', + src : ['<%= paths.sass.target %>', '<%= paths.sass.exclude %>'], + ext : '<%= paths.sass.ext %>' + }] + }, + themes : { + options : { + //includePaths : require('node-bourbon').includePaths + }, + files : [{ + expand : true, + cwd : 'themes/', + src : ['*/**/*.scss', '<%= paths.sass.exclude %>'], + dest : '<%= paths.sass.dest %>/', + srcd : '<%= paths.sass.src %>/', + ext : '<%= paths.sass.ext %>', + rename : function(dest, matchedSrcPath, options) { + var path = [options.cwd, matchedSrcPath.replace(options.srcd, dest)].join(''); + return path; + } + }] + } +}); + +}; \ No newline at end of file diff --git a/grunt/uglify.js b/grunt/uglify.js new file mode 100644 index 0000000..b6fb8dc --- /dev/null +++ b/grunt/uglify.js @@ -0,0 +1,21 @@ +module.exports = function(grunt) { + +grunt.config('uglify', { + options : { + mangle: false, + report: 'min' + }, + all : { + files : [{ + expand : true, + cwd : '', + dest : '', + src : ['<%= paths.js.files %>'], + rename : function(dest, srcPath) { + return srcPath.replace('/' + grunt.config.get('paths.js.src') + '/', '/' + grunt.config.get('paths.js.dest') + '/'); + } + }] + }, +}); + +}; \ No newline at end of file diff --git a/grunt/watch.js b/grunt/watch.js new file mode 100644 index 0000000..653b1f7 --- /dev/null +++ b/grunt/watch.js @@ -0,0 +1,57 @@ +module.exports = function(grunt) { + +grunt.config('watch', { + phplint : { + files : '<%= paths.php.files_std %>', + tasks : ['phplint'], + options : { + spawn : false + } + }, + sass_core : { + files : ['<%= paths.sass.base_src %>/**/*.scss'], + tasks : ['sass:core'] + }, + sass_themes : { + files : ['themes/**/<%= paths.sass.src %>/**/*.scss'], + tasks : ['sass:themes'] + }, + jshint : { + files : '<%= paths.js.files_std %>', + tasks : ['jshint:all'], + options : { + spawn : false + } + }, + js : { + files : '<%= paths.js.files_std %>', + tasks : ['jshint:all', 'uglify:all'], + options : { + spawn : false + } + } +}); + +grunt.event.on('watch', function(action, filepath) { + // Determine task based on filepath + var get_ext = function(path) { + var ret = ''; + var i = path.lastIndexOf('.'); + if ( -1 !== i && i <= path.length ) { + ret = path.substr(i + 1); + } + return ret; + }; + switch ( get_ext(filepath) ) { + // PHP + case 'php' : + grunt.config('paths.php.files', [filepath]); + break; + // JavaScript + case 'js' : + grunt.config('paths.js.files', [filepath]); + break; + } +}); + +}; \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..053f305 --- /dev/null +++ b/package.json @@ -0,0 +1,20 @@ +{ + "name": "cornerstone", + "version": "0.0.0-dev", + "title": "Cornerstone", + "description": "Enhanced content management for Wordpress", + "author": "Archetyped ", + "license": "GPLv2", + "private": true, + "devDependencies": { + "grunt": "^0.4.5", + "grunt-contrib-jshint": "^0.11.2", + "grunt-contrib-uglify": "^0.9.1", + "grunt-contrib-watch": "^0.6.1", + "grunt-phplint": "0.0.5", + "grunt-sass": "^1.0.0", + "jshint-stylish": "^2.0.1", + "load-grunt-tasks": "^3.2.0", + "time-grunt": "^1.2.1" + } +} From d884a9ebc193a5d96d88a919dd8b8f764a7317ad Mon Sep 17 00:00:00 2001 From: SM Date: Wed, 22 Jul 2015 09:15:43 -1000 Subject: [PATCH 21/24] Update readme (0.7.2) --- readme.txt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/readme.txt b/readme.txt index 05ebad3..45a9663 100644 --- a/readme.txt +++ b/readme.txt @@ -56,6 +56,11 @@ Post your questions/comments at [Cornerstone's official issue tracker](https://g 1. Quickly modify a post's section == Changelog == += 0.7.2 = +* Update: WordPress 4.2.2 support +* Optimize: Autoload classes +* Optimize: Remove legacy PHP code + = 0.7.1 = * Update: WordPress 3.8 support * Update: Readme content (description, features, links) From 82b0498c8c0f33b73539ba5cf1a267845dac38f9 Mon Sep 17 00:00:00 2001 From: SM Date: Wed, 22 Jul 2015 09:16:57 -1000 Subject: [PATCH 22/24] Update: Development version (0.0.0-dev) --- main.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.php b/main.php index 49c08b8..41eb73d 100644 --- a/main.php +++ b/main.php @@ -3,7 +3,7 @@ Plugin Name: Cornerstone Plugin URI: http://archetyped.com/tools/cornerstone/ Description: Enhanced content management for Wordpress -Version: dev (BETA) +Version: 0.0.0-dev Author: Archetyped Author URI: http://archetyped.com Support URI: https://github.com/archetyped/cornerstone/wiki/Reporting-Issues From c71de99e84c00a9df2138ea9fbc3ebb257845f4a Mon Sep 17 00:00:00 2001 From: SM Date: Thu, 23 Jul 2015 08:05:12 -1000 Subject: [PATCH 23/24] Update: WordPress version support (4.2.3) --- readme.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/readme.txt b/readme.txt index 45a9663..ff37bf2 100644 --- a/readme.txt +++ b/readme.txt @@ -4,7 +4,7 @@ Donate: https://gum.co/cnr-donate Tags: cornerstone, cms, content, management, system, structure, organization, sections Plugin Link: http://archetyped.com/tools/cornerstone/ Requires at least: 4.2.2 -Tested up to: 4.2.2 +Tested up to: 4.2.3 Stable tag: trunk Enhanced content management for Wordpress @@ -57,7 +57,7 @@ Post your questions/comments at [Cornerstone's official issue tracker](https://g == Changelog == = 0.7.2 = -* Update: WordPress 4.2.2 support +* Update: WordPress 4.2.3 support * Optimize: Autoload classes * Optimize: Remove legacy PHP code From bb1da27b3c9f5c55166ba6c6c86c2b3f4366e0e5 Mon Sep 17 00:00:00 2001 From: SM Date: Thu, 23 Jul 2015 08:19:37 -1000 Subject: [PATCH 24/24] Prepare release (0.7.2) --- main.php | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/main.php b/main.php index 41eb73d..c3f04fb 100644 --- a/main.php +++ b/main.php @@ -3,7 +3,7 @@ Plugin Name: Cornerstone Plugin URI: http://archetyped.com/tools/cornerstone/ Description: Enhanced content management for Wordpress -Version: 0.0.0-dev +Version: 0.7.2 Author: Archetyped Author URI: http://archetyped.com Support URI: https://github.com/archetyped/cornerstone/wiki/Reporting-Issues diff --git a/package.json b/package.json index 053f305..ce9fff4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cornerstone", - "version": "0.0.0-dev", + "version": "0.7.2", "title": "Cornerstone", "description": "Enhanced content management for Wordpress", "author": "Archetyped ",