diff --git a/README.md b/README.md index 55d93a7d..7b9f52e1 100644 --- a/README.md +++ b/README.md @@ -1,311 +1,314 @@ -# Posts 2 Posts - -Contributors: scribu, ciobi -Tags: connections, custom post types, relationships, many-to-many, users -Requires at least: 3.9 -Tested up to: 4.3 -Stable tag: 1.6.5 -License: GPLv2 or later -License URI: http://www.gnu.org/licenses/gpl-2.0.html - -Efficient many-to-many connections between posts, pages, custom post types, users. - -## Description - -This plugin allows you to create many-to-many relationships between posts of any type: post, page, custom etc. A few example use cases: - -* manually curated lists of related posts -* post series -* products connected to retailers -* etc. - -Additionally, you can create many-to-many relationships between posts and users. So, you could also implement: - -* favorite posts of users -* multiple authors per post -* etc. - -### Support & Maintenance - -I, scribu, will not be offering support (either free or paid) for this plugin anymore. - -Furthermore, I will not be doing any development work whatsoever. - -If you want to fix a bug in the plugin or add new features, feel free to fork it [on github](https://github.com/scribu/wp-posts-to-posts). I will not be accepting any pull requests though, since ensuring that a code change doesn't break things takes effort, effort which I'm no longer willing to expend. - -Links: [**Documentation**](http://github.com/scribu/wp-posts-to-posts/wiki) | [Plugin News](http://scribu.net/wordpress/posts-to-posts) | [Author's Site](http://scribu.net) - -## Installation - -See [Installing Plugins](http://codex.wordpress.org/Managing_Plugins#Installing_Plugins). - -After activating it, refer to the [Basic usage](https://github.com/scribu/wp-posts-to-posts/wiki/Basic-usage) tutorial. - -Additional info can be found on the [wiki](http://github.com/scribu/wp-posts-to-posts/wiki). - -## Frequently Asked Questions - -### The waiting icon keeps spinning forever. - -[Check for JavaScript errors](http://codex.wordpress.org/Using_Your_Browser_to_Diagnose_JavaScript_Errors). If it's an AJAX request, check its output. - -## Screenshots - -1. Basic connection metabox -2. Advanced connection metabox -3. Admin column -4. Widget -5. Connection Types screen - -## Changelog - -### 1.6.5 -* fixed error when Mustache is already loaded. props ApatheticG -* fixed WP_User_Query warning. props PatelUtkarsh -* added Chinese translation. props iwillhappy1314 - -### 1.6.4 -* added Danish translation. props phh -* updated Swedish translation. props EyesX -* fixed issue with multiple `parse_query` calls. props hezachenary -* added `p2p_post_admin_column_link` and `p2p_user_admin_column_link` filters. props PareshRadadiya - -### 1.6.3 -* added Serbian translation. props Borisa Djuraskovic -* fixed spinner in admin box. props yamablam -* fixed JavaScript error related to Backbone. props ericandrewlewis -* made 'p2p_connected_title' filter work for users too. props MZAWeb -* added support for 'dropdown_title' labels. props GaryJones -* made `get_related()` consider all connected items - -### 1.6.2 -* fixed URL query handling. props ntns -* store `WP_Error` instance instead of calling `trigger_error()`. props MZAWeb -* fixed warning when used with Multilingual Press. props dimadin -* introduced `p2p_connected_title` filter. props petitphp - -### 1.6.1 -* fixed user column handling. props versusbassz -* fixed PHP strict standards warnings. props meloniq -* added Estonian translation. props RistoNiinemets -* added Finnish translation. props danielck - -### 1.6 -* introduced `p2p_candidate_title` filter -* introduced JavaScript API -* added Japanese translation -* various refactorings - -### 1.5.2 -* fixed get_prev() and get_next() -* introduced get_adjacent_items() -* fixed admin column titles -* made admin column titles show up before the post date. props luk3thomas -* added 'help' key to 'from_labels' and 'to_labels' arrays. props tareq1988 - -### 1.5.1 -* fix fatal error on activation. props benmay - -### 1.5 -* added [admin dropdowns](https://github.com/scribu/wp-posts-to-posts/wiki/Admin-dropdown-display) -* fixed SQL error related to user connections -* fixed 'labels' handling and added 'column_title' subkey -* refactor metabox JavaScript using Backbone.js -* lazy-load connection candidates, for faster page loads -* lazy-load PHP classes using `spl_register_autoload()` - -### 1.4.3 -* various bug fixes -* added 'inline' mode for shortcodes -* replaced 'trash' icon with 'minus' icon -* pass direction to 'default_cb' - -### 1.4.2 -* fixed each_connected() returning wrapped objects -* fixed issue with user queries and get_current_screen() -* fixed "Delete all connections" button -* fixed bugs with reciprocal and non-reciprocal indeterminate connection types -* added Dutch translation - -### 1.4.1 -* fixed errors in admin box -* fixed each_connected() - -### 1.4 -* added 'p2p_init' hook -* replaced 'View All' button with '+ Create connections' toggle -* improved usability of connection candidate UI -* fixed issues related to auto-drafts -* show columns on the admin user list screen -* [more info](http://scribu.net/wordpress/posts-to-posts/p2p-1-4.html) - -### 1.3.1 -* sanitize connection fields values on save, preventing security exploits -* improved connection field default value handling -* added 'default_cb' as an optional key when defining connection fields -* fixed parameter order for 'p2p_admin_box_show' filter -* pass the current post ID to the 'p2p_new_post_args' filter - -### 1.3 -* allow passing entire objects to get_connected(), connect() etc. -* made get_related() work with posts-to-users connections -* made each_connected() work with simple array of posts -* introduced [p2p_connected] and [p2p_related] shortcodes -* allow 'default' parameter in 'fields' array -* [more info](http://scribu.net/wordpress/posts-to-posts/p2p-1-3.html) - -### 1.2 -* added Tools -> Connection Types admin screen -* fixed migration script -* made p2p_get_connections() accept arrays of ids -* added 'separator' parameter to p2p_list_posts() -* made P2P_Directed_Type->connect() return WP_Error instances instead of just false -* when a user is deleted, delete all the associated connections -* fixed conflict with bbPress Topics for Posts plugin -* [more info](http://scribu.net/wordpress/posts-to-posts/p2p-1-2.html) - -### 1.1.6 -* convert "View All" tab into button -* refresh candidate list after deleting a connection -* fix cardinality check -* introduce 'p2p_connection_type_args' filter -* make 'connected_type' accept an array of connection type names -* inadvertently remove support for queries without 'connected_type' parameter - -### 1.1.5 -* added P2P_Connection_Type->replace() method -* added 'self_connections' flag to p2p_register_connection_type() -* made P2P_Connection_Type->each_connected() work for posts-to-users connections -* made admin list table columns work for posts-to-users connections -* fixed 'from_labels' and 'to_labels' parameters -* fixed search being limited only to post titles - -### 1.1.4 -* show attachment thumbnail instead of title -* merged 'from_object' into 'from' and 'to_object' into 'to' -* made posts-to-users queries respect 'to_query_vars' args -* added $prop_name parameter to P2P_Type::each_connected() -* fixed connection field name conflict - -### 1.1.3 -* fixed regression related to posts-to-users direction -* fixed admin columns overwriting each other -* fixed incorrect direction in admin column links -* added notices when connection type is not properly defined - -### 1.1.2 -* fixed fields not being saved for posts-to-users connections -* fixed missing "New Post" tab in admin box -* fixed notice when deleting post - -### 1.1.1 -* fixed faulty scbFramework loading -* simplified syntax for defining posts-to-users connection types - -### 1.1 -* add p2p_type column to the wp_p2p table -* new low-level api: p2p_create_connection(), p2p_get_connections(), p2p_delete_connections(), p2p_connection_exists() -* support posts-to-users and users-to-posts connection types in the admin -* add 'from_labels' and 'to_labels' args to p2p_register_connection_type() -* [more info](http://scribu.net/wordpress/posts-to-posts/p2p-1-1.html) - -### 1.0.1 -* don't show metabox at all if user doesn't have the required capability -* fix checkbox handling when there are no other input fields -* improve metabox styling -* rename 'show_ui' to 'admin_box' -* add 'admin_column' parameter - -### 1.0 -* widget can now list related posts -* add P2P_Connection_Type::get_related() method -* add 'can_create_post' arg to p2p_register_connection_type() -* two-box mode for `'reciprocal' => false` -* more options for 'show_ui' -* allow checkboxes, radio buttons and textareas as connection fields -* allow drag & drop ordering in both directions -* added get_previous(), get_next() and get_adjacent() methods to P2P_Connection_Type -* [more info](http://scribu.net/wordpress/posts-to-posts/p2p-1-0.html) - -### 0.9.5 -* add '{from|to}_query_vars' args to p2p_register_connection_type() -* add 'cardinality' arg to p2p_register_connection_type() -* add 'id' arg and p2p_type() function -* introduce p2p_split_posts() -* remove p2p_connect(), p2p_disconnect() and p2p_get_connected() -* [more info](http://scribu.net/wordpress/posts-to-posts/p2p-0-9-5.html) - -### 0.9.2 -* fix incorrect storage when creating a connection from the other end -* respect 'reciprocal' => false when 'from' == 'to' -* pass pagination numbers through number_format_i18n() - -### 0.9.1 -* fix bug with each_connected() -* add widget -* allow 'from' and 'to' to be arrays again -* improve RTL support - -### 0.9 -* introduce dropdown connection fields -* introduce 'sortable' arg to p2p_register_connection_type() -* introduce 'data' arg to p2p_register_connection_type() -* replace 'box' arg with hooks -* replace p2p_each_connected() with P2P_Post_Type->each_connected() -* allow using 'connected_meta' and 'connected_orderby' together -* fix some translations -* [more info](http://scribu.net/wordpress/posts-to-posts/p2p-0-9.html) - -### 0.8 -* added ability to create draft posts from the connection box. props Oren Kolker -* show post status in the connection box. props [Michael Fields](http://wordpress.mfields.org/) -* reduced number of queries by caching connection information -* revamped p2p_each_connected() -* introduced p2p_list_posts() -* introduced 'connected_orderby', 'connected_order' and 'connected_order_num' query vars -* [more info](http://scribu.net/wordpress/posts-to-posts/p2p-0-8.html) - -### 0.7 -* improved UI. props [Alex Ciobica](http://ciobi.ca/) -* added 'fields', 'context' and 'prevent_duplicates' args to p2p_register_connection_type() -* [more info](http://scribu.net/wordpress/posts-to-posts/p2p-0-7.html) - -### 0.6 -* added p2p_each_connected() -* fixed p2p_is_connected() -* made p2p_get_connected() return p2p_ids even with `$direction = 'any'` -* made compatible with [Proper Network Activation](http://wordpress.org/extend/plugins/proper-network-activation) -* [more info](http://scribu.net/wordpress/posts-to-posts/version-0-6.html) - -### 0.5.1 -* fixed fatal error on Menus screen - -### 0.5 -* added 'connected_meta' var to WP_Query -* attach p2p_id to each post found via WP_Query -* 'connected_to' => 'any' etc. -* $data parameter can also be a meta_query -* metabox bugfixes -* fixed l10n loading -* [more info](http://scribu.net/wordpress/posts-to-posts/p2p-0-5.html) - -### 0.4 -* introduced 'connected_from', 'connected_to', 'connected' vars to WP_Query -* replaced $reciprocal with $data as the third argument -* p2p_register_connection_type() accepts an associative array as arguments -* removed p2p_list_connected() -* added p2p_delete_connection() -* [more info](http://scribu.net/wordpress/posts-to-posts/p2p-0-4.html) - -### 0.3 -* store connections using a taxonomy instead of postmeta -* [more info](http://scribu.net/wordpress/posts-to-posts/p2p-0-3.html) - -### 0.2 -* UI that supports multiple related posts. props [Patrik Bón](http://www.mrhead.sk/) -* added p2p_list_connected() template tag -* [more info](http://scribu.net/wordpress/posts-to-posts/p2p-0-2.html) - -### 0.1 -* initial release -* [more info](http://scribu.net/wordpress/posts-to-posts/p2p-0-1.html) +# Posts 2 Posts + +Contributors: scribu, ciobi +Tags: connections, custom post types, relationships, many-to-many, users +Requires at least: 3.9 +Tested up to: 4.3 +Stable tag: 1.6.5 +License: GPLv2 or later +License URI: http://www.gnu.org/licenses/gpl-2.0.html + +Efficient many-to-many connections between posts, pages, custom post types, users. + +## Description + +This plugin allows you to create many-to-many relationships between posts of any type: post, page, custom etc. A few example use cases: + +* manually curated lists of related posts +* post series +* products connected to retailers +* etc. + +Additionally, you can create many-to-many relationships between posts and users. So, you could also implement: + +* favorite posts of users +* multiple authors per post +* etc. + +### Support & Maintenance + +I, scribu, will not be offering support (either free or paid) for this plugin anymore. + +Furthermore, I will not be doing any development work whatsoever. + +If you want to fix a bug in the plugin or add new features, feel free to fork it [on github](https://github.com/scribu/wp-posts-to-posts). I will not be accepting any pull requests though, since ensuring that a code change doesn't break things takes effort, effort which I'm no longer willing to expend. + +Links: [**Documentation**](http://github.com/scribu/wp-posts-to-posts/wiki) | [Plugin News](http://scribu.net/wordpress/posts-to-posts) | [Author's Site](http://scribu.net) + +## Installation + +See [Installing Plugins](http://codex.wordpress.org/Managing_Plugins#Installing_Plugins). + +After activating it, refer to the [Basic usage](https://github.com/scribu/wp-posts-to-posts/wiki/Basic-usage) tutorial. + +Additional info can be found on the [wiki](http://github.com/scribu/wp-posts-to-posts/wiki). + +## Frequently Asked Questions + +### The waiting icon keeps spinning forever. + +[Check for JavaScript errors](http://codex.wordpress.org/Using_Your_Browser_to_Diagnose_JavaScript_Errors). If it's an AJAX request, check its output. + +## Screenshots + +1. Basic connection metabox +2. Advanced connection metabox +3. Admin column +4. Widget +5. Connection Types screen + +## Changelog + +### 1.6.5.0.1 +* Added ability to use wp post meta as well as the built in posts 2 posts meta. + +### 1.6.5 +* fixed error when Mustache is already loaded. props ApatheticG +* fixed WP_User_Query warning. props PatelUtkarsh +* added Chinese translation. props iwillhappy1314 + +### 1.6.4 +* added Danish translation. props phh +* updated Swedish translation. props EyesX +* fixed issue with multiple `parse_query` calls. props hezachenary +* added `p2p_post_admin_column_link` and `p2p_user_admin_column_link` filters. props PareshRadadiya + +### 1.6.3 +* added Serbian translation. props Borisa Djuraskovic +* fixed spinner in admin box. props yamablam +* fixed JavaScript error related to Backbone. props ericandrewlewis +* made 'p2p_connected_title' filter work for users too. props MZAWeb +* added support for 'dropdown_title' labels. props GaryJones +* made `get_related()` consider all connected items + +### 1.6.2 +* fixed URL query handling. props ntns +* store `WP_Error` instance instead of calling `trigger_error()`. props MZAWeb +* fixed warning when used with Multilingual Press. props dimadin +* introduced `p2p_connected_title` filter. props petitphp + +### 1.6.1 +* fixed user column handling. props versusbassz +* fixed PHP strict standards warnings. props meloniq +* added Estonian translation. props RistoNiinemets +* added Finnish translation. props danielck + +### 1.6 +* introduced `p2p_candidate_title` filter +* introduced JavaScript API +* added Japanese translation +* various refactorings + +### 1.5.2 +* fixed get_prev() and get_next() +* introduced get_adjacent_items() +* fixed admin column titles +* made admin column titles show up before the post date. props luk3thomas +* added 'help' key to 'from_labels' and 'to_labels' arrays. props tareq1988 + +### 1.5.1 +* fix fatal error on activation. props benmay + +### 1.5 +* added [admin dropdowns](https://github.com/scribu/wp-posts-to-posts/wiki/Admin-dropdown-display) +* fixed SQL error related to user connections +* fixed 'labels' handling and added 'column_title' subkey +* refactor metabox JavaScript using Backbone.js +* lazy-load connection candidates, for faster page loads +* lazy-load PHP classes using `spl_register_autoload()` + +### 1.4.3 +* various bug fixes +* added 'inline' mode for shortcodes +* replaced 'trash' icon with 'minus' icon +* pass direction to 'default_cb' + +### 1.4.2 +* fixed each_connected() returning wrapped objects +* fixed issue with user queries and get_current_screen() +* fixed "Delete all connections" button +* fixed bugs with reciprocal and non-reciprocal indeterminate connection types +* added Dutch translation + +### 1.4.1 +* fixed errors in admin box +* fixed each_connected() + +### 1.4 +* added 'p2p_init' hook +* replaced 'View All' button with '+ Create connections' toggle +* improved usability of connection candidate UI +* fixed issues related to auto-drafts +* show columns on the admin user list screen +* [more info](http://scribu.net/wordpress/posts-to-posts/p2p-1-4.html) + +### 1.3.1 +* sanitize connection fields values on save, preventing security exploits +* improved connection field default value handling +* added 'default_cb' as an optional key when defining connection fields +* fixed parameter order for 'p2p_admin_box_show' filter +* pass the current post ID to the 'p2p_new_post_args' filter + +### 1.3 +* allow passing entire objects to get_connected(), connect() etc. +* made get_related() work with posts-to-users connections +* made each_connected() work with simple array of posts +* introduced [p2p_connected] and [p2p_related] shortcodes +* allow 'default' parameter in 'fields' array +* [more info](http://scribu.net/wordpress/posts-to-posts/p2p-1-3.html) + +### 1.2 +* added Tools -> Connection Types admin screen +* fixed migration script +* made p2p_get_connections() accept arrays of ids +* added 'separator' parameter to p2p_list_posts() +* made P2P_Directed_Type->connect() return WP_Error instances instead of just false +* when a user is deleted, delete all the associated connections +* fixed conflict with bbPress Topics for Posts plugin +* [more info](http://scribu.net/wordpress/posts-to-posts/p2p-1-2.html) + +### 1.1.6 +* convert "View All" tab into button +* refresh candidate list after deleting a connection +* fix cardinality check +* introduce 'p2p_connection_type_args' filter +* make 'connected_type' accept an array of connection type names +* inadvertently remove support for queries without 'connected_type' parameter + +### 1.1.5 +* added P2P_Connection_Type->replace() method +* added 'self_connections' flag to p2p_register_connection_type() +* made P2P_Connection_Type->each_connected() work for posts-to-users connections +* made admin list table columns work for posts-to-users connections +* fixed 'from_labels' and 'to_labels' parameters +* fixed search being limited only to post titles + +### 1.1.4 +* show attachment thumbnail instead of title +* merged 'from_object' into 'from' and 'to_object' into 'to' +* made posts-to-users queries respect 'to_query_vars' args +* added $prop_name parameter to P2P_Type::each_connected() +* fixed connection field name conflict + +### 1.1.3 +* fixed regression related to posts-to-users direction +* fixed admin columns overwriting each other +* fixed incorrect direction in admin column links +* added notices when connection type is not properly defined + +### 1.1.2 +* fixed fields not being saved for posts-to-users connections +* fixed missing "New Post" tab in admin box +* fixed notice when deleting post + +### 1.1.1 +* fixed faulty scbFramework loading +* simplified syntax for defining posts-to-users connection types + +### 1.1 +* add p2p_type column to the wp_p2p table +* new low-level api: p2p_create_connection(), p2p_get_connections(), p2p_delete_connections(), p2p_connection_exists() +* support posts-to-users and users-to-posts connection types in the admin +* add 'from_labels' and 'to_labels' args to p2p_register_connection_type() +* [more info](http://scribu.net/wordpress/posts-to-posts/p2p-1-1.html) + +### 1.0.1 +* don't show metabox at all if user doesn't have the required capability +* fix checkbox handling when there are no other input fields +* improve metabox styling +* rename 'show_ui' to 'admin_box' +* add 'admin_column' parameter + +### 1.0 +* widget can now list related posts +* add P2P_Connection_Type::get_related() method +* add 'can_create_post' arg to p2p_register_connection_type() +* two-box mode for `'reciprocal' => false` +* more options for 'show_ui' +* allow checkboxes, radio buttons and textareas as connection fields +* allow drag & drop ordering in both directions +* added get_previous(), get_next() and get_adjacent() methods to P2P_Connection_Type +* [more info](http://scribu.net/wordpress/posts-to-posts/p2p-1-0.html) + +### 0.9.5 +* add '{from|to}_query_vars' args to p2p_register_connection_type() +* add 'cardinality' arg to p2p_register_connection_type() +* add 'id' arg and p2p_type() function +* introduce p2p_split_posts() +* remove p2p_connect(), p2p_disconnect() and p2p_get_connected() +* [more info](http://scribu.net/wordpress/posts-to-posts/p2p-0-9-5.html) + +### 0.9.2 +* fix incorrect storage when creating a connection from the other end +* respect 'reciprocal' => false when 'from' == 'to' +* pass pagination numbers through number_format_i18n() + +### 0.9.1 +* fix bug with each_connected() +* add widget +* allow 'from' and 'to' to be arrays again +* improve RTL support + +### 0.9 +* introduce dropdown connection fields +* introduce 'sortable' arg to p2p_register_connection_type() +* introduce 'data' arg to p2p_register_connection_type() +* replace 'box' arg with hooks +* replace p2p_each_connected() with P2P_Post_Type->each_connected() +* allow using 'connected_meta' and 'connected_orderby' together +* fix some translations +* [more info](http://scribu.net/wordpress/posts-to-posts/p2p-0-9.html) + +### 0.8 +* added ability to create draft posts from the connection box. props Oren Kolker +* show post status in the connection box. props [Michael Fields](http://wordpress.mfields.org/) +* reduced number of queries by caching connection information +* revamped p2p_each_connected() +* introduced p2p_list_posts() +* introduced 'connected_orderby', 'connected_order' and 'connected_order_num' query vars +* [more info](http://scribu.net/wordpress/posts-to-posts/p2p-0-8.html) + +### 0.7 +* improved UI. props [Alex Ciobica](http://ciobi.ca/) +* added 'fields', 'context' and 'prevent_duplicates' args to p2p_register_connection_type() +* [more info](http://scribu.net/wordpress/posts-to-posts/p2p-0-7.html) + +### 0.6 +* added p2p_each_connected() +* fixed p2p_is_connected() +* made p2p_get_connected() return p2p_ids even with `$direction = 'any'` +* made compatible with [Proper Network Activation](http://wordpress.org/extend/plugins/proper-network-activation) +* [more info](http://scribu.net/wordpress/posts-to-posts/version-0-6.html) + +### 0.5.1 +* fixed fatal error on Menus screen + +### 0.5 +* added 'connected_meta' var to WP_Query +* attach p2p_id to each post found via WP_Query +* 'connected_to' => 'any' etc. +* $data parameter can also be a meta_query +* metabox bugfixes +* fixed l10n loading +* [more info](http://scribu.net/wordpress/posts-to-posts/p2p-0-5.html) + +### 0.4 +* introduced 'connected_from', 'connected_to', 'connected' vars to WP_Query +* replaced $reciprocal with $data as the third argument +* p2p_register_connection_type() accepts an associative array as arguments +* removed p2p_list_connected() +* added p2p_delete_connection() +* [more info](http://scribu.net/wordpress/posts-to-posts/p2p-0-4.html) + +### 0.3 +* store connections using a taxonomy instead of postmeta +* [more info](http://scribu.net/wordpress/posts-to-posts/p2p-0-3.html) + +### 0.2 +* UI that supports multiple related posts. props [Patrik Bón](http://www.mrhead.sk/) +* added p2p_list_connected() template tag +* [more info](http://scribu.net/wordpress/posts-to-posts/p2p-0-2.html) + +### 0.1 +* initial release +* [more info](http://scribu.net/wordpress/posts-to-posts/p2p-0-1.html) diff --git a/admin/box-factory.php b/admin/box-factory.php index 8bbe7c8f..50dc70d2 100644 --- a/admin/box-factory.php +++ b/admin/box-factory.php @@ -1,153 +1,349 @@ - 'side', - 'priority' => 'default', - 'can_create_post' => true - ) ); - - return $box_args; - } - - function add_meta_boxes( $post_type ) { - $this->filter( 'post', $post_type ); - } - - function add_item( $directed, $object_type, $post_type, $title ) { - if ( !self::show_box( $directed, $GLOBALS['post'] ) ) - return; - - $box = $this->create_box( $directed ); - $box_args = $this->queue[ $directed->name ]; - - add_meta_box( - sprintf( 'p2p-%s-%s', $directed->get_direction(), $directed->name ), - $title, - array( $box, 'render' ), - $post_type, - $box_args->context, - $box_args->priority - ); - - $box->init_scripts(); - } - - private static function show_box( $directed, $post ) { - $show = $directed->get( 'opposite', 'side' )->can_edit_connections(); - - return apply_filters( 'p2p_admin_box_show', $show, $directed, $post ); - } - - private function create_box( $directed ) { - $box_args = $this->queue[ $directed->name ]; - - $title_class = str_replace( 'P2P_Side_', 'P2P_Field_Title_', - get_class( $directed->get( 'opposite', 'side' ) ) ); - - $columns = array( - 'delete' => new P2P_Field_Delete, - 'title' => new $title_class( $directed->get( 'opposite', 'labels' )->singular_name ), - ); - - foreach ( $directed->fields as $key => $data ) { - $columns[ 'meta-' . $key ] = new P2P_Field_Generic( $key, $data ); - } - - if ( $orderby_key = $directed->get_orderby_key() ) { - $columns['order'] = new P2P_Field_Order( $orderby_key ); - } - - return new P2P_Box( $box_args, $columns, $directed ); - } - - /** - * Collect metadata from all boxes. - */ - function save_post( $post_id, $post ) { - if ( 'revision' == $post->post_type || defined( 'DOING_AJAX' ) ) - return; - - if ( isset( $_POST['p2p_connections'] ) ) { - // Loop through the hidden fields instead of through $_POST['p2p_meta'] because empty checkboxes send no data. - foreach ( $_POST['p2p_connections'] as $p2p_id ) { - $data = scbForms::get_value( array( 'p2p_meta', $p2p_id ), $_POST, array() ); - - $connection = p2p_get_connection( $p2p_id ); - - if ( ! $connection ) - continue; - - $fields = p2p_type( $connection->p2p_type )->fields; - - foreach ( $fields as $key => &$field ) { - $field['name'] = $key; - } - - $data = scbForms::validate_post_data( $fields, $data ); - - scbForms::update_meta( $fields, $data, $p2p_id, 'p2p' ); - } - } - - // Ordering - if ( isset( $_POST['p2p_order'] ) ) { - foreach ( $_POST['p2p_order'] as $key => $list ) { - foreach ( $list as $i => $p2p_id ) { - p2p_update_meta( $p2p_id, $key, $i ); - } - } - } - } - - /** - * Controller for all box ajax requests. - */ - function wp_ajax_p2p_box() { - check_ajax_referer( P2P_BOX_NONCE, 'nonce' ); - - $ctype = p2p_type( $_REQUEST['p2p_type'] ); - if ( !$ctype || !isset( $this->queue[$ctype->name] ) ) - die(0); - - $directed = $ctype->set_direction( $_REQUEST['direction'] ); - if ( !$directed ) - die(0); - - $post = get_post( $_REQUEST['from'] ); - if ( !$post ) - die(0); - - if ( !self::show_box( $directed, $post ) ) - die(-1); - - $box = $this->create_box( $directed ); - - $method = 'ajax_' . $_REQUEST['subaction']; - - $box->$method(); - } -} - + 'side', + 'priority' => 'default', + 'can_create_post' => true + ) ); + + return $box_args; + } + + function add_meta_boxes( $post_type ) { + $this->filter( 'post', $post_type ); + } + + function add_item( $directed, $object_type, $post_type, $title ) { + if ( !self::show_box( $directed, $GLOBALS['post'] ) ) + return; + + $box = $this->create_box( $directed ); + $box_args = $this->queue[ $directed->name ]; + + add_meta_box( + sprintf( 'p2p-%s-%s', $directed->get_direction(), $directed->name ), + $title, + array( $box, 'render' ), + $post_type, + $box_args->context, + $box_args->priority + ); + + $box->init_scripts(); + } + + private static function show_box( $directed, $post ) { + $show = $directed->get( 'opposite', 'side' )->can_edit_connections(); + + return apply_filters( 'p2p_admin_box_show', $show, $directed, $post ); + } + + private function create_box( $directed ) { + $box_args = $this->queue[ $directed->name ]; + + $title_class = str_replace( 'P2P_Side_', 'P2P_Field_Title_', + get_class( $directed->get( 'opposite', 'side' ) ) ); + + $columns = array( + 'delete' => new P2P_Field_Delete, + 'title' => new $title_class( $directed->get( 'opposite', 'labels' )->singular_name ), + ); + + foreach ( $directed->fields as $key => $data ) { + $columns[ 'meta-' . $key ] = new P2P_Field_Generic( $key, $data ); + } + + if ( $orderby_key = $directed->get_orderby_key() ) { + $columns['order'] = new P2P_Field_Order( $orderby_key ); + } + + return new P2P_Box( $box_args, $columns, $directed ); + } + + /** + * Collect metadata from all boxes. + */ + function save_post( $post_id, $post ) { + if ( 'revision' == $post->post_type || defined( 'DOING_AJAX' ) ) + return; + + if ( isset( $_POST['p2p_connections'] ) ) { + // Loop through the hidden fields instead of through $_POST['p2p_meta'] because empty checkboxes send no data. + foreach ( $_POST['p2p_connections'] as $p2p_id ) { + $data = scbForms::get_value( array( 'p2p_meta', $p2p_id ), $_POST, array() ); + + $connection = p2p_get_connection( $p2p_id ); + + if ( ! $connection ) + continue; + + + + //$postmeta_save = p2p_type( $connection->p2p_type )->postmeta_save; + //echo '
..........................';
+				//print_r( p2p_type( $connection->p2p_type ));
+				$p2p_connection_info = p2p_type( $connection->p2p_type);	//post type? user/post name
+				//print_r( $p2p_connection_info );
+				//echo $p2p_connection_info->save_meta_type;
+				
+				//echo '
'; + + //echo '..........................
'; + //exit('--------------------'); + + if($p2p_connection_info->save_meta_type) + $save_as_metatype = $p2p_connection_info->save_meta_type; + else + $save_as_metatype = ''; + + /* + exit ($save_as_metatype); + + +[fields] => Array + ( + [from_type] => Array + ( + [title] => usertestar + [p2p_type] => user + [type] => text + ) + + [to_type] => Array + ( + [title] => clienttestar + [p2p_type] => client + [type] => text + ) + + ) + + [name] => user_to_client + [data] => Array + ( + ) + + */ + + $fields = p2p_type( $connection->p2p_type )->fields; + + foreach ( $fields as $key => &$field ) { + $field['name'] = $key; + } + + $data = scbForms::validate_post_data( $fields, $data ); + + /* + echo '
';
+				print_r($fields);
+				echo '
'; + exit(); + */ + if ($save_as_metatype == 'wp') + $this->save_post_meta($post_id, $post, $fields, $connection); + elseif ($save_as_metatype == 'p2p') + scbForms::update_meta( $fields, $data, $p2p_id, 'p2p' ); + else + scbForms::update_meta( $fields, $data, $p2p_id, 'p2p' ); + } + } + + // Ordering + if ( isset( $_POST['p2p_order'] ) ) { + foreach ( $_POST['p2p_order'] as $key => $list ) { + foreach ( $list as $i => $p2p_id ) { + p2p_update_meta( $p2p_id, $key, $i ); + } + } + } + } + + /** + * Controller for all box ajax requests. + */ + function wp_ajax_p2p_box() { + check_ajax_referer( P2P_BOX_NONCE, 'nonce' ); + + $ctype = p2p_type( $_REQUEST['p2p_type'] ); + if ( !$ctype || !isset( $this->queue[$ctype->name] ) ) + die(0); + + $directed = $ctype->set_direction( $_REQUEST['direction'] ); + if ( !$directed ) + die(0); + + $post = get_post( $_REQUEST['from'] ); + if ( !$post ) + die(0); + + if ( !self::show_box( $directed, $post ) ) + die(-1); + + $box = $this->create_box( $directed ); + + $method = 'ajax_' . $_REQUEST['subaction']; + + $box->$method(); + } + + /** + * Save p2p connection, from and to, as post meta if save_wp_meta is set to true + */ + + + function save_post_meta($post_id, $post, $fields, $connection = array()) { + /* + echo '
'; + + echo '
';
+		print_r( ( $connection ));
+		echo '
'; + + + echo '
'; + //print_r($connection); + echo '
'; + echo $post->ID; + echo '
';
+		echo $connection->p2p_type;
+		echo '
'; + + echo '

'; + echo '
';
+		print_r($fields);
+		echo '
';
+		echo '
'; + echo '
';
+		//print_r($data);
+		echo '
'; + echo '
'; + exit('-----------------------------------------------------'); + + exit('field - data - '. $p2p_id. ' - p2p'); + + + die(); + + if ($fields){ + if ($connection->p2p_from == $post->ID){ + update_post_meta($post->ID, 'p2p_'.$connection->p2p_type.'_to', $connection->p2p_to); + update_post_meta($connection->p2p_from, 'p2p_'.$connection->p2p_type.'_from', $connection->p2p_from); + } + if ($connection->p2p_to == $post->ID){ + update_post_meta($post->ID, 'p2p_'.$connection->p2p_type.'_from', $connection->p2p_from); + update_post_meta($connection->p2p_from, 'p2p_'.$connection->p2p_type.'_to', $connection->p2p_to); + } + }else{ + scbForms::update_meta( $fields, $data, $p2p_id, 'p2p' ); + } + + */ + + //if ($fields){ + if ($connection->p2p_from == $post->ID){ + //Get old meta value + $new_meta_value = $connection->p2p_to; + + $old_meta_value = get_post_meta( $post->ID, 'p2p_'.$connection->p2p_type.'_to', true ); + //check for duplicate values, if so remove them + // explode on , and remove spaces + //$old_meta_value = implode(',',array_unique(explode(',', $old_meta_value))); + + $updated_meta_value = $old_meta_value.', '.$new_meta_value; // save as a comma seperated string + + + $test_arr = array_unique(array_map('trim', explode(',', $updated_meta_value))); + $test_arr = array_filter($test_arr); + + //echo '
';
+				//print_r($test_arr);
+				//echo '
'; + //exit('----------------'); + //$save_meta_value = implode(',',); + + $save_meta_value = implode(',', $test_arr); + //$save_meta_arr = array_unique(explode(',', $save_meta_value)); + + update_post_meta($post->ID, 'p2p_'.$connection->p2p_type.'_to', $save_meta_value); + + //Get old meta value + $new_meta_value = $connection->p2p_from; + $old_meta_value = get_post_meta( $connection->p2p_from, 'p2p_'.$connection->p2p_type.'_from', true ); + //$old_meta_value = implode(',',array_unique(explode(',', $old_meta_value))); + $updated_meta_value = $old_meta_value.', '.$new_meta_value; // save as a comma seperated string + $test_arr = array_unique(array_map('trim', explode(',', $updated_meta_value))); + $test_arr = array_filter($test_arr); + + //echo '
';
+				//print_r($test_arr);
+				//echo '
'; + //exit('----------------'); + + $save_meta_value = implode(',', $test_arr); + + update_post_meta($connection->p2p_from, 'p2p_'.$connection->p2p_type.'_from', $save_meta_value); + } + if ($connection->p2p_to == $post->ID){ + + //Get old meta value + $new_meta_value = $connection->p2p_from; + $old_meta_value = get_post_meta( $post->ID, 'p2p_'.$connection->p2p_type.'_from', true ); + //$old_meta_value = implode(',',array_unique(explode(',', $old_meta_value))); + $updated_meta_value = $old_meta_value.', '.$new_meta_value; // save as a comma seperated string + $test_arr = array_unique(array_map('trim', explode(',', $updated_meta_value))); + $test_arr = array_filter($test_arr); + + //echo '
';
+				//print_r($test_arr);
+				//echo '
'; + //exit('----------------'); + + $save_meta_value = implode(',', $test_arr); + + update_post_meta($post->ID, 'p2p_'.$connection->p2p_type.'_from', $save_meta_value); + + //Get old meta value + $new_meta_value = $connection->p2p_to; + $old_meta_value = get_post_meta( $connection->p2p_from, 'p2p_'.$connection->p2p_type.'_to', true ); + + $updated_meta_value = $old_meta_value.', '.$new_meta_value; // save as a comma seperated string + + $test_arr = array_unique(array_map('trim', explode(',', $updated_meta_value))); + $test_arr = array_filter($test_arr); + + //echo '
';
+				//print_r($test_arr);
+				//echo '
'; + //exit('----------------'); + + $save_meta_value = implode(',', $test_arr); + + update_post_meta($connection->p2p_from, 'p2p_'.$connection->p2p_type.'_to', $save_meta_value); + } + //} + } +} + diff --git a/admin/box.css b/admin/box.css index f3520f67..6e802826 100644 --- a/admin/box.css +++ b/admin/box.css @@ -1,228 +1,228 @@ -.p2p-placeholder { - color: #AAA; -} - -.p2p-box td a { - text-decoration: none; -} - -.p2p-toggle-tabs { - font-weight: bold; -} - -/* Tabs */ - -.p2p-box .tabs-panel { - border: 1px solid #DFDFDF; - display: none; - padding: 10px; -} - -.p2p-box p.help { - color: #666; - font-style: normal; -} - -.p2p-box .p2p-tab-search { - display: block; -} - -.tabs-panel { - border-radius: 0 3px 3px 3px; -} - -/* Connections */ - -.p2p-results, -.p2p-connections { - background-color: #F9F9F9; - border-color: #DFDFDF; - border-radius: 3px; - border-spacing: 0; - border-style: solid; - border-width: 1px; - width: 100%; -} - -.p2p-box .tabs-panel button { - clear: both; - display: block; - margin-top: 8px; -} - -.p2p-search + .p2p-results, -.p2p-search + .p2p-notice, -.p2p-results + .p2p-navigation { - margin-top: 8px; -} - -.p2p-results td, -.p2p-connections td { - border-top: 1px solid #DFDFDF; -} - -.p2p-connections th, -.p2p-connections td, -.p2p-results td { - padding: 3px 6px; -} - -th.p2p-col-delete, -td.p2p-col-delete, -td.p2p-col-create { - padding: 0; -} - -.p2p-results tr:first-child td { - border-top: 0; -} - -.p2p-connections th { - background-color: #F1F1F1; - text-align: left; -} - -.p2p-connections td input[type="text"] { - width: 100%; -} - -.p2p-col-delete { - width: 16px; - border-right: 1px solid transparent; -} - -.p2p-icon { - display: block; - height: 16px; - width: 16px; - opacity: 0.5; -} - -.p2p-col-create div, -.p2p-icon { - padding: 5px 6px; - vertical-align: middle; -} - -td.p2p-col-create .p2p-icon { - padding: 0; -} - -.p2p-col-create:hover, -.p2p-col-delete:hover { - background-color: #f5f5f5; - border-right-color: #ddd; - cursor: pointer; -} - -.p2p-col-create:hover .p2p-icon, -.p2p-col-delete:hover .p2p-icon { - opacity: 1; -} - -.p2p-col-delete .p2p-icon { - background: url("images/minus.png") no-repeat scroll 50% 50% transparent; -} - -.p2p-col-create .p2p-icon { - background: url("images/plus.png") no-repeat scroll 50% 50% transparent; - float: left; - margin-right: 6px; -} - -.p2p-box .post-state { - font-style: italic; -} - -.p2p-box td img { - display: block; - margin: 2px 0; - max-height: 60px; - max-width: 80px; -} - -td.p2p-col-order { - width: 13px; -} - -tr:hover td.p2p-col-order { - cursor: move; - background: url("images/sort.png") no-repeat scroll 50% 50% transparent; -} - -.p2p-search .p2p-spinner { - margin-left: -20px; - opacity: 0.8; - vertical-align: -0.3em; -} - -.p2p-tab-search input { - background-color: transparent; - padding-right: 22px; - margin: 0; -} - -.p2p-tab-create-post input[type="text"] { - width: 100%; - display: block; -} - -/* Pagination */ - -.p2p-navigation { - clear: both; - display: table; - overflow: hidden; -} - -.p2p-navigation .button.inactive, -.p2p-navigation .button.inactive:hover { - border-color: #bbb; - color: #aaa; - background: #f6f6f6; - text-shadow: none; - cursor: default; -} - -.p2p-navigation div { - float: left; - line-height: 13px; - padding: 6px 5px 0; -} - -.p2p-navigation .p2p-spinner { - margin-top: 5px; - margin-left: 5px; -} - - -/* RTL support */ - -body.rtl .p2p-navigation div { - float: right; -} - -body.rtl .p2p-search .p2p-spinner { - margin-left: 0; - margin-right: -20px; -} - -body.rtl .p2p-tab-search input { - padding-left: 22px; - padding-right: 0; -} - -body.rtl .p2p-navigation .p2p-spinner { - margin-right: 5px; -} - -body.rtl .p2p-col-create, -body.rtl .p2p-col-delete { - border-left: 1px solid transparent; - border-right: 0; -} - -body.rtl .p2p-col-create:hover, -body.rtl .p2p-col-delete:hover { - border-left-color: #ddd; -} +.p2p-placeholder { + color: #AAA; +} + +.p2p-box td a { + text-decoration: none; +} + +.p2p-toggle-tabs { + font-weight: bold; +} + +/* Tabs */ + +.p2p-box .tabs-panel { + border: 1px solid #DFDFDF; + display: none; + padding: 10px; +} + +.p2p-box p.help { + color: #666; + font-style: normal; +} + +.p2p-box .p2p-tab-search { + display: block; +} + +.tabs-panel { + border-radius: 0 3px 3px 3px; +} + +/* Connections */ + +.p2p-results, +.p2p-connections { + background-color: #F9F9F9; + border-color: #DFDFDF; + border-radius: 3px; + border-spacing: 0; + border-style: solid; + border-width: 1px; + width: 100%; +} + +.p2p-box .tabs-panel button { + clear: both; + display: block; + margin-top: 8px; +} + +.p2p-search + .p2p-results, +.p2p-search + .p2p-notice, +.p2p-results + .p2p-navigation { + margin-top: 8px; +} + +.p2p-results td, +.p2p-connections td { + border-top: 1px solid #DFDFDF; +} + +.p2p-connections th, +.p2p-connections td, +.p2p-results td { + padding: 3px 6px; +} + +th.p2p-col-delete, +td.p2p-col-delete, +td.p2p-col-create { + padding: 0; +} + +.p2p-results tr:first-child td { + border-top: 0; +} + +.p2p-connections th { + background-color: #F1F1F1; + text-align: left; +} + +.p2p-connections td input[type="text"] { + width: 100%; +} + +.p2p-col-delete { + width: 16px; + border-right: 1px solid transparent; +} + +.p2p-icon { + display: block; + height: 16px; + width: 16px; + opacity: 0.5; +} + +.p2p-col-create div, +.p2p-icon { + padding: 5px 6px; + vertical-align: middle; +} + +td.p2p-col-create .p2p-icon { + padding: 0; +} + +.p2p-col-create:hover, +.p2p-col-delete:hover { + background-color: #f5f5f5; + border-right-color: #ddd; + cursor: pointer; +} + +.p2p-col-create:hover .p2p-icon, +.p2p-col-delete:hover .p2p-icon { + opacity: 1; +} + +.p2p-col-delete .p2p-icon { + background: url("images/minus.png") no-repeat scroll 50% 50% transparent; +} + +.p2p-col-create .p2p-icon { + background: url("images/plus.png") no-repeat scroll 50% 50% transparent; + float: left; + margin-right: 6px; +} + +.p2p-box .post-state { + font-style: italic; +} + +.p2p-box td img { + display: block; + margin: 2px 0; + max-height: 60px; + max-width: 80px; +} + +td.p2p-col-order { + width: 13px; +} + +tr:hover td.p2p-col-order { + cursor: move; + background: url("images/sort.png") no-repeat scroll 50% 50% transparent; +} + +.p2p-search .p2p-spinner { + margin-left: -20px; + opacity: 0.8; + vertical-align: -0.3em; +} + +.p2p-tab-search input { + background-color: transparent; + padding-right: 22px; + margin: 0; +} + +.p2p-tab-create-post input[type="text"] { + width: 100%; + display: block; +} + +/* Pagination */ + +.p2p-navigation { + clear: both; + display: table; + overflow: hidden; +} + +.p2p-navigation .button.inactive, +.p2p-navigation .button.inactive:hover { + border-color: #bbb; + color: #aaa; + background: #f6f6f6; + text-shadow: none; + cursor: default; +} + +.p2p-navigation div { + float: left; + line-height: 13px; + padding: 6px 5px 0; +} + +.p2p-navigation .p2p-spinner { + margin-top: 5px; + margin-left: 5px; +} + + +/* RTL support */ + +body.rtl .p2p-navigation div { + float: right; +} + +body.rtl .p2p-search .p2p-spinner { + margin-left: 0; + margin-right: -20px; +} + +body.rtl .p2p-tab-search input { + padding-left: 22px; + padding-right: 0; +} + +body.rtl .p2p-navigation .p2p-spinner { + margin-right: 5px; +} + +body.rtl .p2p-col-create, +body.rtl .p2p-col-delete { + border-left: 1px solid transparent; + border-right: 0; +} + +body.rtl .p2p-col-create:hover, +body.rtl .p2p-col-delete:hover { + border-left-color: #ddd; +} diff --git a/admin/box.js b/admin/box.js index 02120ff0..e65f1270 100644 --- a/admin/box.js +++ b/admin/box.js @@ -1,473 +1,473 @@ -(function() { - var Candidate, Candidates, CandidatesView, Connection, Connections, ConnectionsView, CreatePostView, ENTER_KEY, MetaboxView, get_mustache_template, remove_row, row_wait; - - ENTER_KEY = 13; - - row_wait = function($td) { - return $td.find('.p2p-icon').css('background-image', 'url(' + P2PAdminL10n.spinner + ')'); - }; - - remove_row = function($td) { - var $table; - $table = $td.closest('table'); - $td.closest('tr').remove(); - if (!$table.find('tbody tr').length) { - return $table.hide(); - } - }; - - get_mustache_template = function(name) { - return jQuery('#p2p-template-' + name).html(); - }; - - // Class for representing a single connection candidate - Candidate = Backbone.Model.extend({}); - - // Class for representing a single connection - Connection = Backbone.Model.extend({}); - - // Class for holding search parameters; not really a model - Candidates = Backbone.Model.extend({ - - // (Re)perform a search with the current parameters - sync: function() { - var params, _this = this; - params = { - subaction: 'search' - }; - return this.ajax_request(params, function(response) { - var _ref = response.navigation; - _this.total_pages = (_ref ? _ref['total-pages-raw'] : void 0) || 1; - _this.trigger('sync', response); - }); - }, - - // Validation function, called by Backbone when parameters are changed - validate: function(attrs) { - var _ref = attrs.paged; - if (0 < _ref && _ref <= this.total_pages) { - return null; - } - return 'invalid page'; - } - }); - - // Class for holding a list of connections - Connections = Backbone.Collection.extend({ - model: Connection, - - // Create both a candidate item and a connection - createItemAndConnect: function(title) { - var data, _this = this; - data = { - subaction: 'create_post', - post_title: title - }; - return this.ajax_request(data, function(response) { - _this.trigger('create', response); - }); - }, - - // Create a connection from a candidate - create: function(candidate) { - var data, _this = this; - data = { - subaction: 'connect', - to: candidate.get('id') - }; - return this.ajax_request(data, function(response) { - _this.trigger('create', response); - }); - }, - - // Delete a connection - "delete": function(connection) { - var data, _this = this; - data = { - subaction: 'disconnect', - p2p_id: connection.get('id') - }; - return this.ajax_request(data, function(response) { - _this.trigger('delete', response, connection); - }); - }, - - // Delete all connections - clear: function() { - var data, _this = this; - data = { - subaction: 'clear_connections' - }; - return this.ajax_request(data, function(response) { - _this.trigger('clear', response); - }); - } - }); - - // View responsible for the connection list - ConnectionsView = Backbone.View.extend({ - - events: { - 'click th.p2p-col-delete .p2p-icon': 'clear', - 'click td.p2p-col-delete .p2p-icon': 'delete' - }, - - initialize: function(options) { - this.options = options; - this.maybe_make_sortable(); - this.collection.on('create', this.afterCreate, this); - this.collection.on('clear', this.afterClear, this); - }, - - maybe_make_sortable: function() { - if (this.$('th.p2p-col-order').length) { - this.$('tbody').sortable({ - handle: 'td.p2p-col-order', - helper: function(e, ui) { - ui.children().each(function() { - var $this; - $this = jQuery(this); - $this.width($this.width()); - }); - return ui; - } - }); - } - }, - - clear: function(ev) { - var $td; - ev.preventDefault(); - if (!confirm(P2PAdminL10n.deleteConfirmMessage)) { - return; - } - $td = jQuery(ev.target).closest('td'); - row_wait($td); - this.collection.clear(); - }, - - afterClear: function() { - this.$el.hide().find('tbody').html(''); - }, - - "delete": function(ev) { - var $td, req; - ev.preventDefault(); - $td = jQuery(ev.target).closest('td'); - row_wait($td); - req = this.collection["delete"](new Connection({ - id: $td.find('input').val() - })); - req.done(function() { - remove_row($td); - }); - }, - - afterCreate: function(response) { - this.$el.show().find('tbody').append(response.row); - this.collection.trigger('append', response); - } - }); - - // View responsible for the candidate list - CandidatesView = Backbone.View.extend({ - - template: Mustache.compile(get_mustache_template('tab-list')), - - events: { - 'keypress :text': 'handleReturn', - 'keyup :text': 'handleSearch', - 'click .p2p-prev, .p2p-next': 'changePage', - 'click td.p2p-col-create div': 'promote' - }, - - initialize: function(options) { - this.options = options; - this.spinner = options.spinner; - options.connections.on('delete', this.afterCandidatesRefreshed, this); - options.connections.on('clear', this.afterCandidatesRefreshed, this); - this.collection.on('sync', this.afterCandidatesRefreshed, this); - this.collection.on('error', this.afterInvalid, this); - this.collection.on('invalid', this.afterInvalid, this); - }, - - promote: function(ev) { - var $td, req, _this = this; - ev.preventDefault(); - $td = jQuery(ev.target).closest('td'); - row_wait($td); - var candidate = new Candidate({ - id: $td.find('div').data('item-id') - }); - req = this.options.connections.create(candidate); - req.done(function() { - if (_this.options.duplicate_connections) { - $td.find('.p2p-icon').css('background-image', ''); - } else { - remove_row($td); - } - }); - }, - - handleReturn: function(ev) { - if (ev.keyCode === ENTER_KEY) { - ev.preventDefault(); - } - }, - - handleSearch: function(ev) { - var $searchInput, delayed, - _this = this; - if (delayed !== void 0) { - clearTimeout(delayed); - } - $searchInput = jQuery(ev.target); - delayed = setTimeout(function() { - var searchStr; - searchStr = $searchInput.val(); - if (searchStr === _this.collection.get('s')) { - return; - } - _this.spinner.insertAfter($searchInput).show(); - _this.collection.save({ - 's': searchStr, - 'paged': 1 - }); - }, 400); - }, - - changePage: function(ev) { - var $navButton, new_page; - $navButton = jQuery(ev.currentTarget); - new_page = this.collection.get('paged'); - if ($navButton.hasClass('p2p-prev')) { - new_page--; - } else { - new_page++; - } - this.spinner.appendTo(this.$('.p2p-navigation')); - this.collection.save('paged', new_page); - }, - - afterCandidatesRefreshed: function(response) { - this.spinner.remove(); - this.$('button, .p2p-results, .p2p-navigation, .p2p-notice').remove(); - if ('string' !== typeof response) { - response = this.template(response); - } - this.$el.append(response); - }, - afterInvalid: function() { - this.spinner.remove(); - } - }); - - // View responsible for the post creation UI - CreatePostView = Backbone.View.extend({ - - events: { - 'click button': 'createItem', - 'keypress :text': 'handleReturn' - }, - - initialize: function(options) { - this.options = options; - this.createButton = this.$('button'); - this.createInput = this.$(':text'); - }, - - handleReturn: function(ev) { - if (ev.keyCode === ENTER_KEY) { - this.createButton.click(); - ev.preventDefault(); - } - }, - - createItem: function(ev) { - var req, title, _this = this; - ev.preventDefault(); - if (this.createButton.hasClass('inactive')) { - return false; - } - title = this.createInput.val(); - if (title === '') { - this.createInput.focus(); - return; - } - this.createButton.addClass('inactive'); - req = this.collection.createItemAndConnect(title); - req.done(function() { - _this.createInput.val(''); - _this.createButton.removeClass('inactive'); - }); - } - }); - - // View responsible for the entire metabox - MetaboxView = Backbone.View.extend({ - - events: { - 'click .p2p-toggle-tabs': 'toggleTabs', - 'click .wp-tab-bar li': 'setActiveTab' - }, - - initialize: function(options) { - this.options = options; - this.spinner = options.spinner; - this.initializedCandidates = false; - options.connections.on('append', this.afterConnectionAppended, this); - options.connections.on('clear', this.afterConnectionDeleted, this); - options.connections.on('delete', this.afterConnectionDeleted, this); - }, - - toggleTabs: function(ev) { - var $tabs; - ev.preventDefault(); - $tabs = this.$('.p2p-create-connections-tabs'); - $tabs.toggle(); - if (!this.initializedCandidates && $tabs.is(':visible')) { - this.options.candidates.sync(); - this.initializedCandidates = true; - } - }, - - setActiveTab: function(ev) { - var $tab; - ev.preventDefault(); - $tab = jQuery(ev.currentTarget); - this.$('.wp-tab-bar li').removeClass('wp-tab-active'); - $tab.addClass('wp-tab-active'); - this.$el.find('.tabs-panel').hide().end().find($tab.data('ref')).show().find(':text').focus(); - }, - - afterConnectionAppended: function(response) { - if ('one' === this.options.cardinality) { - this.$('.p2p-create-connections').hide(); - } - }, - - afterConnectionDeleted: function(response) { - if ('one' === this.options.cardinality) { - this.$('.p2p-create-connections').show(); - } - } - }); - - window.P2PAdmin = { - Candidate: Candidate, - Connection: Connection, - boxes: {} - }; - - jQuery(function() { - // Polyfill for browsers that don't support the placeholder attribute - if (!jQuery('')[0].placeholder) { - function setVal() { - var $this; - $this = jQuery(this); - if (!$this.val()) { - $this.val($this.attr('placeholder')); - $this.addClass('p2p-placeholder'); - } - }; - - function clearVal() { - var $this; - $this = jQuery(this); - if ($this.hasClass('p2p-placeholder')) { - $this.val(''); - $this.removeClass('p2p-placeholder'); - } - }; - jQuery('.p2p-search input[placeholder]').each(setVal).focus(clearVal).blur(setVal); - } - - Mustache.compilePartial('table-row', get_mustache_template('table-row')); - - jQuery('.p2p-box').each(function() { - var $metabox, $spinner, candidates, candidatesView, connections, connectionsView, createPostView, ctype, metaboxView; - - $metabox = jQuery(this); - - $spinner = jQuery('', { - 'src': P2PAdminL10n.spinner, - 'class': 'p2p-spinner' - }); - - candidates = new Candidates({ - 's': '', - 'paged': 1 - }); - candidates.total_pages = $metabox.find('.p2p-total').data('num') || 1; - - ctype = { - p2p_type: $metabox.data('p2p_type'), - direction: $metabox.data('direction'), - from: jQuery('#post_ID').val() - }; - - // All ajax requests should be done through this function - function ajax_request(options, callback) { - var params = _.extend({}, options, candidates.attributes, ctype, { - action: 'p2p_box', - nonce: P2PAdminL10n.nonce - }); - - return jQuery.post(ajaxurl, params, function(response) { - var e; - try { - response = jQuery.parseJSON(response); - } catch (_error) { - e = _error; - if (typeof console !== "undefined" && console !== null) { - console.error('Malformed response', response); - } - return; - } - if (response.error) { - return alert(response.error); - } else { - return callback(response); - } - }); - } - - candidates.ajax_request = ajax_request; - - connections = new Connections(); - connections.ajax_request = ajax_request; - - connectionsView = new ConnectionsView({ - el: $metabox.find('.p2p-connections'), - collection: connections, - candidates: candidates - }); - - candidatesView = new CandidatesView({ - el: $metabox.find('.p2p-tab-search'), - collection: candidates, - connections: connections, - spinner: $spinner, - duplicate_connections: $metabox.data('duplicate_connections') - }); - - createPostView = new CreatePostView({ - el: $metabox.find('.p2p-tab-create-post'), - collection: connections - }); - - metaboxView = new MetaboxView({ - el: $metabox, - spinner: $spinner, - cardinality: $metabox.data('cardinality'), - candidates: candidates, - connections: connections - }); - - P2PAdmin.boxes[ctype.p2p_type] = { - candidates: candidates, - connections: connections - }; - }); - }); -}()); +(function() { + var Candidate, Candidates, CandidatesView, Connection, Connections, ConnectionsView, CreatePostView, ENTER_KEY, MetaboxView, get_mustache_template, remove_row, row_wait; + + ENTER_KEY = 13; + + row_wait = function($td) { + return $td.find('.p2p-icon').css('background-image', 'url(' + P2PAdminL10n.spinner + ')'); + }; + + remove_row = function($td) { + var $table; + $table = $td.closest('table'); + $td.closest('tr').remove(); + if (!$table.find('tbody tr').length) { + return $table.hide(); + } + }; + + get_mustache_template = function(name) { + return jQuery('#p2p-template-' + name).html(); + }; + + // Class for representing a single connection candidate + Candidate = Backbone.Model.extend({}); + + // Class for representing a single connection + Connection = Backbone.Model.extend({}); + + // Class for holding search parameters; not really a model + Candidates = Backbone.Model.extend({ + + // (Re)perform a search with the current parameters + sync: function() { + var params, _this = this; + params = { + subaction: 'search' + }; + return this.ajax_request(params, function(response) { + var _ref = response.navigation; + _this.total_pages = (_ref ? _ref['total-pages-raw'] : void 0) || 1; + _this.trigger('sync', response); + }); + }, + + // Validation function, called by Backbone when parameters are changed + validate: function(attrs) { + var _ref = attrs.paged; + if (0 < _ref && _ref <= this.total_pages) { + return null; + } + return 'invalid page'; + } + }); + + // Class for holding a list of connections + Connections = Backbone.Collection.extend({ + model: Connection, + + // Create both a candidate item and a connection + createItemAndConnect: function(title) { + var data, _this = this; + data = { + subaction: 'create_post', + post_title: title + }; + return this.ajax_request(data, function(response) { + _this.trigger('create', response); + }); + }, + + // Create a connection from a candidate + create: function(candidate) { + var data, _this = this; + data = { + subaction: 'connect', + to: candidate.get('id') + }; + return this.ajax_request(data, function(response) { + _this.trigger('create', response); + }); + }, + + // Delete a connection + "delete": function(connection) { + var data, _this = this; + data = { + subaction: 'disconnect', + p2p_id: connection.get('id') + }; + return this.ajax_request(data, function(response) { + _this.trigger('delete', response, connection); + }); + }, + + // Delete all connections + clear: function() { + var data, _this = this; + data = { + subaction: 'clear_connections' + }; + return this.ajax_request(data, function(response) { + _this.trigger('clear', response); + }); + } + }); + + // View responsible for the connection list + ConnectionsView = Backbone.View.extend({ + + events: { + 'click th.p2p-col-delete .p2p-icon': 'clear', + 'click td.p2p-col-delete .p2p-icon': 'delete' + }, + + initialize: function(options) { + this.options = options; + this.maybe_make_sortable(); + this.collection.on('create', this.afterCreate, this); + this.collection.on('clear', this.afterClear, this); + }, + + maybe_make_sortable: function() { + if (this.$('th.p2p-col-order').length) { + this.$('tbody').sortable({ + handle: 'td.p2p-col-order', + helper: function(e, ui) { + ui.children().each(function() { + var $this; + $this = jQuery(this); + $this.width($this.width()); + }); + return ui; + } + }); + } + }, + + clear: function(ev) { + var $td; + ev.preventDefault(); + if (!confirm(P2PAdminL10n.deleteConfirmMessage)) { + return; + } + $td = jQuery(ev.target).closest('td'); + row_wait($td); + this.collection.clear(); + }, + + afterClear: function() { + this.$el.hide().find('tbody').html(''); + }, + + "delete": function(ev) { + var $td, req; + ev.preventDefault(); + $td = jQuery(ev.target).closest('td'); + row_wait($td); + req = this.collection["delete"](new Connection({ + id: $td.find('input').val() + })); + req.done(function() { + remove_row($td); + }); + }, + + afterCreate: function(response) { + this.$el.show().find('tbody').append(response.row); + this.collection.trigger('append', response); + } + }); + + // View responsible for the candidate list + CandidatesView = Backbone.View.extend({ + + template: Mustache.compile(get_mustache_template('tab-list')), + + events: { + 'keypress :text': 'handleReturn', + 'keyup :text': 'handleSearch', + 'click .p2p-prev, .p2p-next': 'changePage', + 'click td.p2p-col-create div': 'promote' + }, + + initialize: function(options) { + this.options = options; + this.spinner = options.spinner; + options.connections.on('delete', this.afterCandidatesRefreshed, this); + options.connections.on('clear', this.afterCandidatesRefreshed, this); + this.collection.on('sync', this.afterCandidatesRefreshed, this); + this.collection.on('error', this.afterInvalid, this); + this.collection.on('invalid', this.afterInvalid, this); + }, + + promote: function(ev) { + var $td, req, _this = this; + ev.preventDefault(); + $td = jQuery(ev.target).closest('td'); + row_wait($td); + var candidate = new Candidate({ + id: $td.find('div').data('item-id') + }); + req = this.options.connections.create(candidate); + req.done(function() { + if (_this.options.duplicate_connections) { + $td.find('.p2p-icon').css('background-image', ''); + } else { + remove_row($td); + } + }); + }, + + handleReturn: function(ev) { + if (ev.keyCode === ENTER_KEY) { + ev.preventDefault(); + } + }, + + handleSearch: function(ev) { + var $searchInput, delayed, + _this = this; + if (delayed !== void 0) { + clearTimeout(delayed); + } + $searchInput = jQuery(ev.target); + delayed = setTimeout(function() { + var searchStr; + searchStr = $searchInput.val(); + if (searchStr === _this.collection.get('s')) { + return; + } + _this.spinner.insertAfter($searchInput).show(); + _this.collection.save({ + 's': searchStr, + 'paged': 1 + }); + }, 400); + }, + + changePage: function(ev) { + var $navButton, new_page; + $navButton = jQuery(ev.currentTarget); + new_page = this.collection.get('paged'); + if ($navButton.hasClass('p2p-prev')) { + new_page--; + } else { + new_page++; + } + this.spinner.appendTo(this.$('.p2p-navigation')); + this.collection.save('paged', new_page); + }, + + afterCandidatesRefreshed: function(response) { + this.spinner.remove(); + this.$('button, .p2p-results, .p2p-navigation, .p2p-notice').remove(); + if ('string' !== typeof response) { + response = this.template(response); + } + this.$el.append(response); + }, + afterInvalid: function() { + this.spinner.remove(); + } + }); + + // View responsible for the post creation UI + CreatePostView = Backbone.View.extend({ + + events: { + 'click button': 'createItem', + 'keypress :text': 'handleReturn' + }, + + initialize: function(options) { + this.options = options; + this.createButton = this.$('button'); + this.createInput = this.$(':text'); + }, + + handleReturn: function(ev) { + if (ev.keyCode === ENTER_KEY) { + this.createButton.click(); + ev.preventDefault(); + } + }, + + createItem: function(ev) { + var req, title, _this = this; + ev.preventDefault(); + if (this.createButton.hasClass('inactive')) { + return false; + } + title = this.createInput.val(); + if (title === '') { + this.createInput.focus(); + return; + } + this.createButton.addClass('inactive'); + req = this.collection.createItemAndConnect(title); + req.done(function() { + _this.createInput.val(''); + _this.createButton.removeClass('inactive'); + }); + } + }); + + // View responsible for the entire metabox + MetaboxView = Backbone.View.extend({ + + events: { + 'click .p2p-toggle-tabs': 'toggleTabs', + 'click .wp-tab-bar li': 'setActiveTab' + }, + + initialize: function(options) { + this.options = options; + this.spinner = options.spinner; + this.initializedCandidates = false; + options.connections.on('append', this.afterConnectionAppended, this); + options.connections.on('clear', this.afterConnectionDeleted, this); + options.connections.on('delete', this.afterConnectionDeleted, this); + }, + + toggleTabs: function(ev) { + var $tabs; + ev.preventDefault(); + $tabs = this.$('.p2p-create-connections-tabs'); + $tabs.toggle(); + if (!this.initializedCandidates && $tabs.is(':visible')) { + this.options.candidates.sync(); + this.initializedCandidates = true; + } + }, + + setActiveTab: function(ev) { + var $tab; + ev.preventDefault(); + $tab = jQuery(ev.currentTarget); + this.$('.wp-tab-bar li').removeClass('wp-tab-active'); + $tab.addClass('wp-tab-active'); + this.$el.find('.tabs-panel').hide().end().find($tab.data('ref')).show().find(':text').focus(); + }, + + afterConnectionAppended: function(response) { + if ('one' === this.options.cardinality) { + this.$('.p2p-create-connections').hide(); + } + }, + + afterConnectionDeleted: function(response) { + if ('one' === this.options.cardinality) { + this.$('.p2p-create-connections').show(); + } + } + }); + + window.P2PAdmin = { + Candidate: Candidate, + Connection: Connection, + boxes: {} + }; + + jQuery(function() { + // Polyfill for browsers that don't support the placeholder attribute + if (!jQuery('')[0].placeholder) { + function setVal() { + var $this; + $this = jQuery(this); + if (!$this.val()) { + $this.val($this.attr('placeholder')); + $this.addClass('p2p-placeholder'); + } + }; + + function clearVal() { + var $this; + $this = jQuery(this); + if ($this.hasClass('p2p-placeholder')) { + $this.val(''); + $this.removeClass('p2p-placeholder'); + } + }; + jQuery('.p2p-search input[placeholder]').each(setVal).focus(clearVal).blur(setVal); + } + + Mustache.compilePartial('table-row', get_mustache_template('table-row')); + + jQuery('.p2p-box').each(function() { + var $metabox, $spinner, candidates, candidatesView, connections, connectionsView, createPostView, ctype, metaboxView; + + $metabox = jQuery(this); + + $spinner = jQuery('', { + 'src': P2PAdminL10n.spinner, + 'class': 'p2p-spinner' + }); + + candidates = new Candidates({ + 's': '', + 'paged': 1 + }); + candidates.total_pages = $metabox.find('.p2p-total').data('num') || 1; + + ctype = { + p2p_type: $metabox.data('p2p_type'), + direction: $metabox.data('direction'), + from: jQuery('#post_ID').val() + }; + + // All ajax requests should be done through this function + function ajax_request(options, callback) { + var params = _.extend({}, options, candidates.attributes, ctype, { + action: 'p2p_box', + nonce: P2PAdminL10n.nonce + }); + + return jQuery.post(ajaxurl, params, function(response) { + var e; + try { + response = jQuery.parseJSON(response); + } catch (_error) { + e = _error; + if (typeof console !== "undefined" && console !== null) { + console.error('Malformed response', response); + } + return; + } + if (response.error) { + return alert(response.error); + } else { + return callback(response); + } + }); + } + + candidates.ajax_request = ajax_request; + + connections = new Connections(); + connections.ajax_request = ajax_request; + + connectionsView = new ConnectionsView({ + el: $metabox.find('.p2p-connections'), + collection: connections, + candidates: candidates + }); + + candidatesView = new CandidatesView({ + el: $metabox.find('.p2p-tab-search'), + collection: candidates, + connections: connections, + spinner: $spinner, + duplicate_connections: $metabox.data('duplicate_connections') + }); + + createPostView = new CreatePostView({ + el: $metabox.find('.p2p-tab-create-post'), + collection: connections + }); + + metaboxView = new MetaboxView({ + el: $metabox, + spinner: $spinner, + cardinality: $metabox.data('cardinality'), + candidates: candidates, + connections: connections + }); + + P2PAdmin.boxes[ctype.p2p_type] = { + candidates: candidates, + connections: connections + }; + }); + }); +}()); diff --git a/admin/box.php b/admin/box.php index 09c83d4f..e960c48c 100644 --- a/admin/box.php +++ b/admin/box.php @@ -1,326 +1,326 @@ - false, - 'update_post_meta_cache' => false, - 'post_status' => 'any', - ); - - function __construct( $args, $columns, $ctype ) { - $this->args = $args; - - $this->columns = $columns; - - $this->ctype = $ctype; - - $this->labels = $this->ctype->get( 'opposite', 'labels' ); - } - - public function init_scripts() { - - if ( self::$enqueued_scripts ) - return; - - wp_enqueue_style( 'p2p-box', plugins_url( 'box.css', __FILE__ ), - array(), P2P_PLUGIN_VERSION ); - - wp_register_script( 'mustache', plugins_url( 'mustache.js', __FILE__ ), - array(), '0.7.2', true ); - - wp_enqueue_script( 'p2p-box', plugins_url( 'box.js', __FILE__ ), - array( 'backbone', 'mustache' ), P2P_PLUGIN_VERSION, true ); - - wp_localize_script( 'p2p-box', 'P2PAdminL10n', array( - 'nonce' => wp_create_nonce( P2P_BOX_NONCE ), - 'spinner' => admin_url( 'images/wpspin_light.gif' ), - 'deleteConfirmMessage' => __( 'Are you sure you want to delete all connections?', P2P_TEXTDOMAIN ), - ) ); - - self::$enqueued_scripts = true; - - add_action( 'admin_footer', array( __CLASS__, 'add_templates' ) ); - } - - static function add_templates() { - self::add_template( 'tab-list' ); - self::add_template( 'table-row' ); - } - - private static function add_template( $slug ) { - echo html( 'script', array( - 'type' => 'text/html', - 'id' => "p2p-template-$slug" - ), file_get_contents( dirname( __FILE__ ) . "/templates/$slug.html" ) ); - } - - function render( $item ) { - $extra_qv = array_merge( self::$admin_box_qv, array( - 'p2p:context' => 'admin_box', - 'p2p:per_page' => -1 - ) ); - - $this->connected_items = $this->ctype->get_connected( $item, $extra_qv, 'abstract' )->items; - - $data = array( - 'attributes' => $this->render_data_attributes(), - 'connections' => $this->render_connections_table( $item ), - 'create-connections' => $this->render_create_connections( $item ), - 'help' => isset( $this->labels->help ) ? $this->labels->help : '' - ); - - echo P2P_Mustache::render( 'box', $data ); - } - - protected function render_data_attributes() { - $data_attr = array( - 'p2p_type' => $this->ctype->name, - 'duplicate_connections' => $this->ctype->duplicate_connections, - 'cardinality' => $this->ctype->get( 'opposite', 'cardinality' ), - 'direction' => $this->ctype->get_direction() - ); - - $data_attr_str = array(); - foreach ( $data_attr as $key => $value ) - $data_attr_str[] = "data-$key='" . $value . "'"; - - return implode( ' ', $data_attr_str ); - } - - protected function render_connections_table( $item ) { - $data = array(); - - if ( empty( $this->connected_items ) ) - $data['hide'] = 'style="display:none"'; - - $tbody = array(); - foreach ( $this->connected_items as $item ) { - $tbody[] = $this->connection_row( $item->p2p_id, $item ); - } - $data['tbody'] = $tbody; - - foreach ( $this->columns as $key => $field ) { - $data['thead'][] = array( - 'column' => $key, - 'title' => $field->get_title() - ); - } - - return $data; - } - - protected function render_create_connections( $item ) { - $data = array( - 'label' => $this->labels->create, - ); - - if ( 'one' == $this->ctype->get( 'opposite', 'cardinality' ) ) { - if ( !empty( $this->connected_items ) ) - $data['hide'] = 'style="display:none"'; - } - - // Search tab - $tab_content = P2P_Mustache::render( 'tab-search', array( - 'placeholder' => $this->labels->search_items, - ) ); - - $data['tabs'][] = array( - 'tab-id' => 'search', - 'tab-title' => __( 'Search', P2P_TEXTDOMAIN ), - 'is-active' => array(true), - 'tab-content' => $tab_content - ); - - // "Create post" tab - if ( $this->can_create_post() ) { - $tab_content = P2P_Mustache::render( 'tab-create-post', array( - 'title' => $this->labels->add_new_item - ) ); - - $data['tabs'][] = array( - 'tab-id' => 'create-post', - 'tab-title' => $this->labels->new_item, - 'tab-content' => $tab_content - ); - } - - $data['show-tab-headers'] = count( $data['tabs'] ) > 1 ? array(true) : false; - - return $data; - } - - protected function connection_row( $p2p_id, $item, $render = false ) { - - $item->title = apply_filters( 'p2p_connected_title', $item->get_title(), $item->get_object(), $this->ctype ); - - $data = array(); - - foreach ( $this->columns as $key => $field ) { - $data['columns'][] = array( - 'column' => $key, - 'content' => $field->render( $p2p_id, $item ) - ); - } - - if ( !$render ) - return $data; - - return P2P_Mustache::render( 'table-row', $data ); - } - - protected function candidate_row( $item ) { - $title = apply_filters( 'p2p_candidate_title', $item->get_title(), $item->get_object(), $this->ctype ); - - $title_data = array_merge( $this->columns['title']->get_data( $item ), array( - 'title' => $title, - 'item-id' => $item->get_id(), - ) ); - - $data = array(); - - $data['columns'][] = array( - 'column' => 'create', - 'content' => P2P_Mustache::render( 'column-create', $title_data ) - ); - - return $data; - } - - protected function candidate_rows( $current_post_id, $page = 1, $search = '' ) { - $extra_qv = array_merge( self::$admin_box_qv, array( - 'p2p:context' => 'admin_box_candidates', - 'p2p:search' => $search, - 'p2p:page' => $page, - 'p2p:per_page' => 5 - ) ); - - $candidate = $this->ctype->get_connectable( $current_post_id, $extra_qv, 'abstract' ); - - if ( empty( $candidate->items ) ) { - return html( 'div class="p2p-notice"', $this->labels->not_found ); - } - - $data = array(); - - foreach ( $candidate->items as $item ) { - $data['rows'][] = $this->candidate_row( $item ); - } - - if ( $candidate->total_pages > 1 ) { - $data['navigation'] = array( - 'current-page' => number_format_i18n( $candidate->current_page ), - 'total-pages' => number_format_i18n( $candidate->total_pages ), - - 'total-pages-raw' => $candidate->total_pages, - - 'prev-inactive' => ( 1 == $candidate->current_page ) ? 'inactive' : '', - 'next-inactive' => ( $candidate->total_pages == $candidate->current_page ) ? 'inactive' : '', - - 'prev-label' => __( 'previous', P2P_TEXTDOMAIN ), - 'next-label' => __( 'next', P2P_TEXTDOMAIN ), - 'of-label' => __( 'of', P2P_TEXTDOMAIN ), - ); - } - - return $data; - } - - - // Ajax handlers - - public function ajax_create_post() { - if ( !$this->can_create_post() ) - die( -1 ); - - $args = array( - 'post_title' => $_POST['post_title'], - 'post_author' => get_current_user_id(), - 'post_type' => $this->ctype->get( 'opposite', 'side' )->first_post_type() - ); - - $from = absint( $_POST['from'] ); - - $args = apply_filters( 'p2p_new_post_args', $args, $this->ctype, $from ); - - $this->safe_connect( wp_insert_post( $args ) ); - } - - public function ajax_connect() { - $this->safe_connect( $_POST['to'] ); - } - - private function safe_connect( $to ) { - $from = absint( $_POST['from'] ); - $to = absint( $to ); - - if ( !$from || !$to ) - die(-1); - - $p2p_id = $this->ctype->connect( $from, $to ); - - self::maybe_send_error( $p2p_id ); - - $item = $this->ctype->get( 'opposite','side')->item_recognize( $to ); - - $out = array( - 'row' => $this->connection_row( $p2p_id, $item, true ) - ); - - die( json_encode( $out ) ); - } - - public function ajax_disconnect() { - p2p_delete_connection( $_POST['p2p_id'] ); - - $this->refresh_candidates(); - } - - public function ajax_clear_connections() { - $r = $this->ctype->disconnect( $_POST['from'], 'any' ); - - self::maybe_send_error( $r ); - - $this->refresh_candidates(); - } - - protected static function maybe_send_error( $r ) { - if ( !is_wp_error( $r ) ) - return; - - $out = array( - 'error' => $r->get_error_message() - ); - - die( json_encode( $out ) ); - } - - public function ajax_search() { - $this->refresh_candidates(); - } - - private function refresh_candidates() { - die( json_encode( $this->candidate_rows( - $_REQUEST['from'], $_REQUEST['paged'], $_REQUEST['s'] ) ) ); - } - - protected function can_create_post() { - if ( !$this->args->can_create_post ) - return false; - - $side = $this->ctype->get( 'opposite', 'side' ); - - return $side->can_create_item(); - } -} - + false, + 'update_post_meta_cache' => false, + 'post_status' => 'any', + ); + + function __construct( $args, $columns, $ctype ) { + $this->args = $args; + + $this->columns = $columns; + + $this->ctype = $ctype; + + $this->labels = $this->ctype->get( 'opposite', 'labels' ); + } + + public function init_scripts() { + + if ( self::$enqueued_scripts ) + return; + + wp_enqueue_style( 'p2p-box', plugins_url( 'box.css', __FILE__ ), + array(), P2P_PLUGIN_VERSION ); + + wp_register_script( 'mustache', plugins_url( 'mustache.js', __FILE__ ), + array(), '0.7.2', true ); + + wp_enqueue_script( 'p2p-box', plugins_url( 'box.js', __FILE__ ), + array( 'backbone', 'mustache' ), P2P_PLUGIN_VERSION, true ); + + wp_localize_script( 'p2p-box', 'P2PAdminL10n', array( + 'nonce' => wp_create_nonce( P2P_BOX_NONCE ), + 'spinner' => admin_url( 'images/wpspin_light.gif' ), + 'deleteConfirmMessage' => __( 'Are you sure you want to delete all connections?', P2P_TEXTDOMAIN ), + ) ); + + self::$enqueued_scripts = true; + + add_action( 'admin_footer', array( __CLASS__, 'add_templates' ) ); + } + + static function add_templates() { + self::add_template( 'tab-list' ); + self::add_template( 'table-row' ); + } + + private static function add_template( $slug ) { + echo html( 'script', array( + 'type' => 'text/html', + 'id' => "p2p-template-$slug" + ), file_get_contents( dirname( __FILE__ ) . "/templates/$slug.html" ) ); + } + + function render( $item ) { + $extra_qv = array_merge( self::$admin_box_qv, array( + 'p2p:context' => 'admin_box', + 'p2p:per_page' => -1 + ) ); + + $this->connected_items = $this->ctype->get_connected( $item, $extra_qv, 'abstract' )->items; + + $data = array( + 'attributes' => $this->render_data_attributes(), + 'connections' => $this->render_connections_table( $item ), + 'create-connections' => $this->render_create_connections( $item ), + 'help' => isset( $this->labels->help ) ? $this->labels->help : '' + ); + + echo P2P_Mustache::render( 'box', $data ); + } + + protected function render_data_attributes() { + $data_attr = array( + 'p2p_type' => $this->ctype->name, + 'duplicate_connections' => $this->ctype->duplicate_connections, + 'cardinality' => $this->ctype->get( 'opposite', 'cardinality' ), + 'direction' => $this->ctype->get_direction() + ); + + $data_attr_str = array(); + foreach ( $data_attr as $key => $value ) + $data_attr_str[] = "data-$key='" . $value . "'"; + + return implode( ' ', $data_attr_str ); + } + + protected function render_connections_table( $item ) { + $data = array(); + + if ( empty( $this->connected_items ) ) + $data['hide'] = 'style="display:none"'; + + $tbody = array(); + foreach ( $this->connected_items as $item ) { + $tbody[] = $this->connection_row( $item->p2p_id, $item ); + } + $data['tbody'] = $tbody; + + foreach ( $this->columns as $key => $field ) { + $data['thead'][] = array( + 'column' => $key, + 'title' => $field->get_title() + ); + } + + return $data; + } + + protected function render_create_connections( $item ) { + $data = array( + 'label' => $this->labels->create, + ); + + if ( 'one' == $this->ctype->get( 'opposite', 'cardinality' ) ) { + if ( !empty( $this->connected_items ) ) + $data['hide'] = 'style="display:none"'; + } + + // Search tab + $tab_content = P2P_Mustache::render( 'tab-search', array( + 'placeholder' => $this->labels->search_items, + ) ); + + $data['tabs'][] = array( + 'tab-id' => 'search', + 'tab-title' => __( 'Search', P2P_TEXTDOMAIN ), + 'is-active' => array(true), + 'tab-content' => $tab_content + ); + + // "Create post" tab + if ( $this->can_create_post() ) { + $tab_content = P2P_Mustache::render( 'tab-create-post', array( + 'title' => $this->labels->add_new_item + ) ); + + $data['tabs'][] = array( + 'tab-id' => 'create-post', + 'tab-title' => $this->labels->new_item, + 'tab-content' => $tab_content + ); + } + + $data['show-tab-headers'] = count( $data['tabs'] ) > 1 ? array(true) : false; + + return $data; + } + + protected function connection_row( $p2p_id, $item, $render = false ) { + + $item->title = apply_filters( 'p2p_connected_title', $item->get_title(), $item->get_object(), $this->ctype ); + + $data = array(); + + foreach ( $this->columns as $key => $field ) { + $data['columns'][] = array( + 'column' => $key, + 'content' => $field->render( $p2p_id, $item ) + ); + } + + if ( !$render ) + return $data; + + return P2P_Mustache::render( 'table-row', $data ); + } + + protected function candidate_row( $item ) { + $title = apply_filters( 'p2p_candidate_title', $item->get_title(), $item->get_object(), $this->ctype ); + + $title_data = array_merge( $this->columns['title']->get_data( $item ), array( + 'title' => $title, + 'item-id' => $item->get_id(), + ) ); + + $data = array(); + + $data['columns'][] = array( + 'column' => 'create', + 'content' => P2P_Mustache::render( 'column-create', $title_data ) + ); + + return $data; + } + + protected function candidate_rows( $current_post_id, $page = 1, $search = '' ) { + $extra_qv = array_merge( self::$admin_box_qv, array( + 'p2p:context' => 'admin_box_candidates', + 'p2p:search' => $search, + 'p2p:page' => $page, + 'p2p:per_page' => 5 + ) ); + + $candidate = $this->ctype->get_connectable( $current_post_id, $extra_qv, 'abstract' ); + + if ( empty( $candidate->items ) ) { + return html( 'div class="p2p-notice"', $this->labels->not_found ); + } + + $data = array(); + + foreach ( $candidate->items as $item ) { + $data['rows'][] = $this->candidate_row( $item ); + } + + if ( $candidate->total_pages > 1 ) { + $data['navigation'] = array( + 'current-page' => number_format_i18n( $candidate->current_page ), + 'total-pages' => number_format_i18n( $candidate->total_pages ), + + 'total-pages-raw' => $candidate->total_pages, + + 'prev-inactive' => ( 1 == $candidate->current_page ) ? 'inactive' : '', + 'next-inactive' => ( $candidate->total_pages == $candidate->current_page ) ? 'inactive' : '', + + 'prev-label' => __( 'previous', P2P_TEXTDOMAIN ), + 'next-label' => __( 'next', P2P_TEXTDOMAIN ), + 'of-label' => __( 'of', P2P_TEXTDOMAIN ), + ); + } + + return $data; + } + + + // Ajax handlers + + public function ajax_create_post() { + if ( !$this->can_create_post() ) + die( -1 ); + + $args = array( + 'post_title' => $_POST['post_title'], + 'post_author' => get_current_user_id(), + 'post_type' => $this->ctype->get( 'opposite', 'side' )->first_post_type() + ); + + $from = absint( $_POST['from'] ); + + $args = apply_filters( 'p2p_new_post_args', $args, $this->ctype, $from ); + + $this->safe_connect( wp_insert_post( $args ) ); + } + + public function ajax_connect() { + $this->safe_connect( $_POST['to'] ); + } + + private function safe_connect( $to ) { + $from = absint( $_POST['from'] ); + $to = absint( $to ); + + if ( !$from || !$to ) + die(-1); + + $p2p_id = $this->ctype->connect( $from, $to ); + + self::maybe_send_error( $p2p_id ); + + $item = $this->ctype->get( 'opposite','side')->item_recognize( $to ); + + $out = array( + 'row' => $this->connection_row( $p2p_id, $item, true ) + ); + + die( json_encode( $out ) ); + } + + public function ajax_disconnect() { + p2p_delete_connection( $_POST['p2p_id'] ); + + $this->refresh_candidates(); + } + + public function ajax_clear_connections() { + $r = $this->ctype->disconnect( $_POST['from'], 'any' ); + + self::maybe_send_error( $r ); + + $this->refresh_candidates(); + } + + protected static function maybe_send_error( $r ) { + if ( !is_wp_error( $r ) ) + return; + + $out = array( + 'error' => $r->get_error_message() + ); + + die( json_encode( $out ) ); + } + + public function ajax_search() { + $this->refresh_candidates(); + } + + private function refresh_candidates() { + die( json_encode( $this->candidate_rows( + $_REQUEST['from'], $_REQUEST['paged'], $_REQUEST['s'] ) ) ); + } + + protected function can_create_post() { + if ( !$this->args->can_create_post ) + return false; + + $side = $this->ctype->get( 'opposite', 'side' ); + + return $side->can_create_item(); + } +} + diff --git a/admin/column-factory.php b/admin/column-factory.php index 266d2dac..79a041e2 100644 --- a/admin/column-factory.php +++ b/admin/column-factory.php @@ -1,24 +1,24 @@ -id}_columns", array( $column, 'add_column' ) ); - add_action( 'admin_print_styles', array( $column, 'styles' ) ); - } -} - +id}_columns", array( $column, 'add_column' ) ); + add_action( 'admin_print_styles', array( $column, 'styles' ) ); + } +} + diff --git a/admin/column-post.php b/admin/column-post.php index 3c6e2185..d51f953a 100644 --- a/admin/column-post.php +++ b/admin/column-post.php @@ -1,36 +1,36 @@ -post_type}_posts_custom_column", array( $this, 'display_column' ), 10, 2 ); - } - - protected function get_items() { - global $wp_query; - - return $wp_query->posts; - } - - function get_admin_link( $item ) { - $args = array( - 'connected_type' => $this->ctype->name, - 'connected_direction' => $this->ctype->flip_direction()->get_direction(), - 'connected_items' => $item->get_id(), - 'post_type' => get_current_screen()->post_type - ); - - $admin_link = apply_filters( "p2p_post_admin_column_link", add_query_arg( $args, admin_url( 'edit.php' ) ), $item ); - - return $admin_link; - } - - function display_column( $column, $item_id ) { - echo parent::render_column( $column, $item_id ); - } -} - +post_type}_posts_custom_column", array( $this, 'display_column' ), 10, 2 ); + } + + protected function get_items() { + global $wp_query; + + return $wp_query->posts; + } + + function get_admin_link( $item ) { + $args = array( + 'connected_type' => $this->ctype->name, + 'connected_direction' => $this->ctype->flip_direction()->get_direction(), + 'connected_items' => $item->get_id(), + 'post_type' => get_current_screen()->post_type + ); + + $admin_link = apply_filters( "p2p_post_admin_column_link", add_query_arg( $args, admin_url( 'edit.php' ) ), $item ); + + return $admin_link; + } + + function display_column( $column, $item_id ) { + echo parent::render_column( $column, $item_id ); + } +} + diff --git a/admin/column-user.php b/admin/column-user.php index de71c7de..bf115f39 100644 --- a/admin/column-user.php +++ b/admin/column-user.php @@ -1,48 +1,48 @@ -items; - } - - // Add the query vars to the global user query (on the user admin screen) - static function user_query( $query ) { - if ( isset( $query->_p2p_capture ) ) - return; - - // Don't overwrite existing P2P query - if ( isset( $query->query_vars['connected_type'] ) ) - return; - - _p2p_append( $query->query_vars, wp_array_slice_assoc( $_GET, - P2P_URL_Query::get_custom_qv() ) ); - } - - function get_admin_link( $item ) { - $args = array( - 'connected_type' => $this->ctype->name, - 'connected_direction' => $this->ctype->flip_direction()->get_direction(), - 'connected_items' => $item->get_id(), - ); - - $admin_link = apply_filters( "p2p_user_admin_column_link", add_query_arg( $args, admin_url( 'users.php' ) ), $item ); - - return $admin_link; - } - - function display_column( $content, $column, $item_id ) { - return $content . parent::render_column( $column, $item_id ); - } -} - +items; + } + + // Add the query vars to the global user query (on the user admin screen) + static function user_query( $query ) { + if ( isset( $query->_p2p_capture ) ) + return; + + // Don't overwrite existing P2P query + if ( isset( $query->query_vars['connected_type'] ) ) + return; + + _p2p_append( $query->query_vars, wp_array_slice_assoc( $_GET, + P2P_URL_Query::get_custom_qv() ) ); + } + + function get_admin_link( $item ) { + $args = array( + 'connected_type' => $this->ctype->name, + 'connected_direction' => $this->ctype->flip_direction()->get_direction(), + 'connected_items' => $item->get_id(), + ); + + $admin_link = apply_filters( "p2p_user_admin_column_link", add_query_arg( $args, admin_url( 'users.php' ) ), $item ); + + return $admin_link; + } + + function display_column( $content, $column, $item_id ) { + return $content . parent::render_column( $column, $item_id ); + } +} + diff --git a/admin/column.php b/admin/column.php index 3f5679cd..02ceaef5 100644 --- a/admin/column.php +++ b/admin/column.php @@ -1,77 +1,77 @@ -ctype = $directed; - - $this->column_id = sprintf( 'p2p-%s-%s', - $this->ctype->get_direction(), - $this->ctype->name - ); - } - - function add_column( $columns ) { - $this->prepare_items(); - - $labels = $this->ctype->get( 'current', 'labels' ); - - $title = isset( $labels->column_title ) - ? $labels->column_title - : $this->ctype->get( 'current', 'title' ); - - return array_splice( $columns, 0, -1 ) + array( $this->column_id => $title ) + $columns; - } - - protected abstract function get_items(); - - protected function prepare_items() { - $items = $this->get_items(); - - $extra_qv = array( - 'p2p:per_page' => -1, - 'p2p:context' => 'admin_column' - ); - - $connected = $this->ctype->get_connected( $items, $extra_qv, 'abstract' ); - - $this->connected = scb_list_group_by( $connected->items, '_p2p_get_other_id' ); - } - - function styles() { -?> - -column_id != $column ) - return; - - if ( !isset( $this->connected[ $item_id ] ) ) - return; - - $out = ''; - - return $out; - } -} - +ctype = $directed; + + $this->column_id = sprintf( 'p2p-%s-%s', + $this->ctype->get_direction(), + $this->ctype->name + ); + } + + function add_column( $columns ) { + $this->prepare_items(); + + $labels = $this->ctype->get( 'current', 'labels' ); + + $title = isset( $labels->column_title ) + ? $labels->column_title + : $this->ctype->get( 'current', 'title' ); + + return array_splice( $columns, 0, -1 ) + array( $this->column_id => $title ) + $columns; + } + + protected abstract function get_items(); + + protected function prepare_items() { + $items = $this->get_items(); + + $extra_qv = array( + 'p2p:per_page' => -1, + 'p2p:context' => 'admin_column' + ); + + $connected = $this->ctype->get_connected( $items, $extra_qv, 'abstract' ); + + $this->connected = scb_list_group_by( $connected->items, '_p2p_get_other_id' ); + } + + function styles() { +?> + +column_id != $column ) + return; + + if ( !isset( $this->connected[ $item_id ] ) ) + return; + + $out = ''; + + return $out; + } +} + diff --git a/admin/dropdown-factory.php b/admin/dropdown-factory.php index e2f8fa18..455aaf8e 100644 --- a/admin/dropdown-factory.php +++ b/admin/dropdown-factory.php @@ -1,19 +1,19 @@ -_p2p_capture ) ) - return; - - // Don't overwrite existing P2P query - if ( isset( $query->query_vars['connected_type'] ) ) - return; - - _p2p_append( $query->query_vars, self::get_qv() ); - } - - protected function render_dropdown() { - return html( 'div', array( - 'style' => 'float: right; margin-left: 16px' - ), - parent::render_dropdown(), - html( 'input', array( - 'type' => 'submit', - 'class' => 'button', - 'value' => __( 'Filter', P2P_TEXTDOMAIN ) - ) ) - ); - } -} - +_p2p_capture ) ) + return; + + // Don't overwrite existing P2P query + if ( isset( $query->query_vars['connected_type'] ) ) + return; + + _p2p_append( $query->query_vars, self::get_qv() ); + } + + protected function render_dropdown() { + return html( 'div', array( + 'style' => 'float: right; margin-left: 16px' + ), + parent::render_dropdown(), + html( 'input', array( + 'type' => 'submit', + 'class' => 'button', + 'value' => __( 'Filter', P2P_TEXTDOMAIN ) + ) ) + ); + } +} + diff --git a/admin/dropdown.php b/admin/dropdown.php index 00c52bad..77bc6a3c 100644 --- a/admin/dropdown.php +++ b/admin/dropdown.php @@ -1,73 +1,73 @@ -ctype = $directed; - $this->title = $title; - } - - function show_dropdown() { - echo $this->render_dropdown(); - } - - protected function render_dropdown() { - $direction = $this->ctype->flip_direction()->get_direction(); - - $labels = $this->ctype->get( 'current', 'labels' ); - - if ( isset( $labels->dropdown_title ) ) - $title = $labels->dropdown_title; - elseif ( isset( $labels->column_title ) ) - $title = $labels->column_title; - else - $title = $this->title; - - return scbForms::input( array( - 'type' => 'select', - 'name' => array( 'p2p', $this->ctype->name, $direction ), - 'choices' => self::get_choices( $this->ctype ), - 'text' => $title, - ), $_GET ); - } - - protected static function get_qv() { - if ( !isset( $_GET['p2p'] ) ) - return array(); - - $args = array(); - - $tmp = reset( $_GET['p2p'] ); - - $args['connected_type'] = key( $_GET['p2p'] ); - - list( $args['connected_direction'], $args['connected_items'] ) = each( $tmp ); - - if ( !$args['connected_items'] ) - return array(); - - return $args; - } - - protected static function get_choices( $directed ) { - $extra_qv = array( - 'p2p:per_page' => -1, - 'p2p:context' => 'admin_dropdown' - ); - - $connected = $directed->get_connected( 'any', $extra_qv, 'abstract' ); - - $options = array(); - foreach ( $connected->items as $item ) - $options[ $item->get_id() ] = $item->get_title(); - - return $options; - } -} - +ctype = $directed; + $this->title = $title; + } + + function show_dropdown() { + echo $this->render_dropdown(); + } + + protected function render_dropdown() { + $direction = $this->ctype->flip_direction()->get_direction(); + + $labels = $this->ctype->get( 'current', 'labels' ); + + if ( isset( $labels->dropdown_title ) ) + $title = $labels->dropdown_title; + elseif ( isset( $labels->column_title ) ) + $title = $labels->column_title; + else + $title = $this->title; + + return scbForms::input( array( + 'type' => 'select', + 'name' => array( 'p2p', $this->ctype->name, $direction ), + 'choices' => self::get_choices( $this->ctype ), + 'text' => $title, + ), $_GET ); + } + + protected static function get_qv() { + if ( !isset( $_GET['p2p'] ) ) + return array(); + + $args = array(); + + $tmp = reset( $_GET['p2p'] ); + + $args['connected_type'] = key( $_GET['p2p'] ); + + list( $args['connected_direction'], $args['connected_items'] ) = each( $tmp ); + + if ( !$args['connected_items'] ) + return array(); + + return $args; + } + + protected static function get_choices( $directed ) { + $extra_qv = array( + 'p2p:per_page' => -1, + 'p2p:context' => 'admin_dropdown' + ); + + $connected = $directed->get_connected( 'any', $extra_qv, 'abstract' ); + + $options = array(); + foreach ( $connected->items as $item ) + $options[ $item->get_id() ] = $item->get_title(); + + return $options; + } +} + diff --git a/admin/factory.php b/admin/factory.php index b28fd28f..b7bd4221 100644 --- a/admin/factory.php +++ b/admin/factory.php @@ -1,103 +1,103 @@ -expand_arg( $args ); - - if ( !$sub_args['show'] ) - return false; - - $this->queue[ $ctype->name ] = (object) $sub_args; - } - - // Collect sub-args from main connection type args and set defaults - protected function expand_arg( $args ) { - if ( isset( $args[ $this->key ] ) ) { - $sub_args = $args[ $this->key ]; - - if ( !is_array( $sub_args ) ) { - $sub_args = array( 'show' => $sub_args ); - } - } else { - $sub_args = array( 'show' => false ); - } - - $sub_args = wp_parse_args( $sub_args, array( - 'show' => 'any', - ) ); - - return $sub_args; - } - - // Begin processing item queue for a particular screen. - function add_items() { - $screen = get_current_screen(); - - $screen_map = array( - 'edit' => 'post', - 'users' => 'user' - ); - - if ( !isset( $screen_map[ $screen->base ] ) ) - return; - - $object_type = $screen_map[ $screen->base ]; - - $this->filter( $object_type, $screen->post_type ); - } - - // Filter item queue based on object type. - function filter( $object_type, $post_type ) { - foreach ( $this->queue as $p2p_type => $args ) { - $ctype = p2p_type( $p2p_type ); - - $directions = self::determine_directions( $ctype, $object_type, $post_type, $args->show ); - - $title = self::get_title( $directions, $ctype ); - - foreach ( $directions as $direction ) { - $key = ( 'to' == $direction ) ? 'to' : 'from'; - - $directed = $ctype->set_direction( $direction ); - - $this->add_item( $directed, $object_type, $post_type, $title[$key] ); - } - } - } - - // Produce an item and add it to the screen. - abstract function add_item( $directed, $object_type, $post_type, $title ); - - protected static function get_title( $directions, $ctype ) { - $title = array( - 'from' => $ctype->get_field( 'title', 'from' ), - 'to' => $ctype->get_field( 'title', 'to' ) - ); - - if ( count( $directions ) > 1 && $title['from'] == $title['to'] ) { - $title['from'] .= __( ' (from)', P2P_TEXTDOMAIN ); - $title['to'] .= __( ' (to)', P2P_TEXTDOMAIN ); - } - - return $title; - } - - protected static function determine_directions( $ctype, $object_type, $post_type, $show_ui ) { - $direction = $ctype->direction_from_types( $object_type, $post_type ); - if ( !$direction ) - return array(); - - return $ctype->strategy->directions_for_admin( $direction, $show_ui ); - } -} - +expand_arg( $args ); + + if ( !$sub_args['show'] ) + return false; + + $this->queue[ $ctype->name ] = (object) $sub_args; + } + + // Collect sub-args from main connection type args and set defaults + protected function expand_arg( $args ) { + if ( isset( $args[ $this->key ] ) ) { + $sub_args = $args[ $this->key ]; + + if ( !is_array( $sub_args ) ) { + $sub_args = array( 'show' => $sub_args ); + } + } else { + $sub_args = array( 'show' => false ); + } + + $sub_args = wp_parse_args( $sub_args, array( + 'show' => 'any', + ) ); + + return $sub_args; + } + + // Begin processing item queue for a particular screen. + function add_items() { + $screen = get_current_screen(); + + $screen_map = array( + 'edit' => 'post', + 'users' => 'user' + ); + + if ( !isset( $screen_map[ $screen->base ] ) ) + return; + + $object_type = $screen_map[ $screen->base ]; + + $this->filter( $object_type, $screen->post_type ); + } + + // Filter item queue based on object type. + function filter( $object_type, $post_type ) { + foreach ( $this->queue as $p2p_type => $args ) { + $ctype = p2p_type( $p2p_type ); + + $directions = self::determine_directions( $ctype, $object_type, $post_type, $args->show ); + + $title = self::get_title( $directions, $ctype ); + + foreach ( $directions as $direction ) { + $key = ( 'to' == $direction ) ? 'to' : 'from'; + + $directed = $ctype->set_direction( $direction ); + + $this->add_item( $directed, $object_type, $post_type, $title[$key] ); + } + } + } + + // Produce an item and add it to the screen. + abstract function add_item( $directed, $object_type, $post_type, $title ); + + protected static function get_title( $directions, $ctype ) { + $title = array( + 'from' => $ctype->get_field( 'title', 'from' ), + 'to' => $ctype->get_field( 'title', 'to' ) + ); + + if ( count( $directions ) > 1 && $title['from'] == $title['to'] ) { + $title['from'] .= __( ' (from)', P2P_TEXTDOMAIN ); + $title['to'] .= __( ' (to)', P2P_TEXTDOMAIN ); + } + + return $title; + } + + protected static function determine_directions( $ctype, $object_type, $post_type, $show_ui ) { + $direction = $ctype->direction_from_types( $object_type, $post_type ); + if ( !$direction ) + return array(); + + return $ctype->strategy->directions_for_admin( $direction, $show_ui ); + } +} + diff --git a/admin/field-delete.php b/admin/field-delete.php index 9f62ca5f..d9c207a8 100644 --- a/admin/field-delete.php +++ b/admin/field-delete.php @@ -1,23 +1,23 @@ - __( 'Delete all connections', P2P_TEXTDOMAIN ) - ); - - return P2P_Mustache::render( 'column-delete-all', $data ); - } - - function render( $p2p_id, $_ ) { - $data = array( - 'p2p_id' => $p2p_id, - 'title' => __( 'Delete connection', P2P_TEXTDOMAIN ) - ); - - return P2P_Mustache::render( 'column-delete', $data ); - } -} - - + __( 'Delete all connections', P2P_TEXTDOMAIN ) + ); + + return P2P_Mustache::render( 'column-delete-all', $data ); + } + + function render( $p2p_id, $_ ) { + $data = array( + 'p2p_id' => $p2p_id, + 'title' => __( 'Delete connection', P2P_TEXTDOMAIN ) + ); + + return P2P_Mustache::render( 'column-delete', $data ); + } +} + + diff --git a/admin/field-generic.php b/admin/field-generic.php index 10e30912..3cbaa0ea 100644 --- a/admin/field-generic.php +++ b/admin/field-generic.php @@ -1,27 +1,27 @@ -key = $key; - $this->data = $data; - } - - function get_title() { - return $this->data['title']; - } - - function render( $p2p_id, $_ ) { - $args = $this->data; - $args['name'] = array( 'p2p_meta', $p2p_id, $this->key ); - - if ( 'select' == $args['type'] && !isset( $args['text'] ) ) - $args['text'] = ''; - - return scbForms::input_from_meta( $args, $p2p_id, 'p2p' ); - } -} - +key = $key; + $this->data = $data; + } + + function get_title() { + return $this->data['title']; + } + + function render( $p2p_id, $_ ) { + $args = $this->data; + $args['name'] = array( 'p2p_meta', $p2p_id, $this->key ); + + if ( 'select' == $args['type'] && !isset( $args['text'] ) ) + $args['text'] = ''; + + return scbForms::input_from_meta( $args, $p2p_id, 'p2p' ); + } +} + diff --git a/admin/field-order.php b/admin/field-order.php index ebdacb7c..74ceb6b8 100644 --- a/admin/field-order.php +++ b/admin/field-order.php @@ -1,23 +1,23 @@ -sort_key = $sort_key; - } - - function get_title() { - return ''; - } - - function render( $p2p_id, $_ ) { - return html( 'input', array( - 'type' => 'hidden', - 'name' => "p2p_order[$this->sort_key][]", - 'value' => $p2p_id - ) ); - } -} - +sort_key = $sort_key; + } + + function get_title() { + return ''; + } + + function render( $p2p_id, $_ ) { + return html( 'input', array( + 'type' => 'hidden', + 'name' => "p2p_order[$this->sort_key][]", + 'value' => $p2p_id + ) ); + } +} + diff --git a/admin/field-title-attachment.php b/admin/field-title-attachment.php index 7677faf8..ee3e79e7 100644 --- a/admin/field-title-attachment.php +++ b/admin/field-title-attachment.php @@ -1,13 +1,13 @@ - $item->get_object()->post_title, - ); - - return $data; - } -} - + $item->get_object()->post_title, + ); + + return $data; + } +} + diff --git a/admin/field-title-post.php b/admin/field-title-post.php index 4750a275..a9ae88ea 100644 --- a/admin/field-title-post.php +++ b/admin/field-title-post.php @@ -1,22 +1,22 @@ - $item->get_permalink() - ); - - $post = $item->get_object(); - - if ( 'publish' != $post->post_status ) { - $status_obj = get_post_status_object( $post->post_status ); - if ( $status_obj ) { - $data['status']['text'] = $status_obj->label; - } - } - - return $data; - } -} - + $item->get_permalink() + ); + + $post = $item->get_object(); + + if ( 'publish' != $post->post_status ) { + $status_obj = get_post_status_object( $post->post_status ); + if ( $status_obj ) { + $data['status']['text'] = $status_obj->label; + } + } + + return $data; + } +} + diff --git a/admin/field-title-user.php b/admin/field-title-user.php index f3c288e8..4503075b 100644 --- a/admin/field-title-user.php +++ b/admin/field-title-user.php @@ -1,11 +1,11 @@ - '', - ); - } -} - + '', + ); + } +} + diff --git a/admin/field-title.php b/admin/field-title.php index 0c77e1a5..2c5dda60 100644 --- a/admin/field-title.php +++ b/admin/field-title.php @@ -1,26 +1,26 @@ -title = $title; - } - - function get_title() { - return $this->title; - } - - function render( $p2p_id, $item ) { - $data = array_merge( $this->get_data( $item ), array( - 'title' => $item->title, - 'url' => $item->get_editlink(), - ) ); - - return P2P_Mustache::render( 'column-title', $data ); - } - - abstract function get_data( $item ); -} - +title = $title; + } + + function get_title() { + return $this->title; + } + + function render( $p2p_id, $item ) { + $data = array_merge( $this->get_data( $item ), array( + 'title' => $item->title, + 'url' => $item->get_editlink(), + ) ); + + return P2P_Mustache::render( 'column-title', $data ); + } + + abstract function get_data( $item ); +} + diff --git a/admin/field.php b/admin/field.php index 9946485f..6714857a 100644 --- a/admin/field.php +++ b/admin/field.php @@ -1,10 +1,10 @@ - - } -}(this, (function () { - - var exports = {}; - - exports.name = "mustache.js"; - exports.version = "0.7.2"; - exports.tags = ["{{", "}}"]; - - exports.Scanner = Scanner; - exports.Context = Context; - exports.Writer = Writer; - - var whiteRe = /\s*/; - var spaceRe = /\s+/; - var nonSpaceRe = /\S/; - var eqRe = /\s*=/; - var curlyRe = /\s*\}/; - var tagRe = /#|\^|\/|>|\{|&|=|!/; - - // Workaround for https://issues.apache.org/jira/browse/COUCHDB-577 - // See https://github.com/janl/mustache.js/issues/189 - function testRe(re, string) { - return RegExp.prototype.test.call(re, string); - } - - function isWhitespace(string) { - return !testRe(nonSpaceRe, string); - } - - var isArray = Array.isArray || function (obj) { - return Object.prototype.toString.call(obj) === "[object Array]"; - }; - - function escapeRe(string) { - return string.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, "\\$&"); - } - - var entityMap = { - "&": "&", - "<": "<", - ">": ">", - '"': '"', - "'": ''', - "/": '/' - }; - - function escapeHtml(string) { - return String(string).replace(/[&<>"'\/]/g, function (s) { - return entityMap[s]; - }); - } - - // Export the escaping function so that the user may override it. - // See https://github.com/janl/mustache.js/issues/244 - exports.escape = escapeHtml; - - function Scanner(string) { - this.string = string; - this.tail = string; - this.pos = 0; - } - - /** - * Returns `true` if the tail is empty (end of string). - */ - Scanner.prototype.eos = function () { - return this.tail === ""; - }; - - /** - * Tries to match the given regular expression at the current position. - * Returns the matched text if it can match, the empty string otherwise. - */ - Scanner.prototype.scan = function (re) { - var match = this.tail.match(re); - - if (match && match.index === 0) { - this.tail = this.tail.substring(match[0].length); - this.pos += match[0].length; - return match[0]; - } - - return ""; - }; - - /** - * Skips all text until the given regular expression can be matched. Returns - * the skipped string, which is the entire tail if no match can be made. - */ - Scanner.prototype.scanUntil = function (re) { - var match, pos = this.tail.search(re); - - switch (pos) { - case -1: - match = this.tail; - this.pos += this.tail.length; - this.tail = ""; - break; - case 0: - match = ""; - break; - default: - match = this.tail.substring(0, pos); - this.tail = this.tail.substring(pos); - this.pos += pos; - } - - return match; - }; - - function Context(view, parent) { - this.view = view; - this.parent = parent; - this.clearCache(); - } - - Context.make = function (view) { - return (view instanceof Context) ? view : new Context(view); - }; - - Context.prototype.clearCache = function () { - this._cache = {}; - }; - - Context.prototype.push = function (view) { - return new Context(view, this); - }; - - Context.prototype.lookup = function (name) { - var value = this._cache[name]; - - if (!value) { - if (name === ".") { - value = this.view; - } else { - var context = this; - - while (context) { - if (name.indexOf(".") > 0) { - var names = name.split("."), i = 0; - - value = context.view; - - while (value && i < names.length) { - value = value[names[i++]]; - } - } else { - value = context.view[name]; - } - - if (value != null) { - break; - } - - context = context.parent; - } - } - - this._cache[name] = value; - } - - if (typeof value === "function") { - value = value.call(this.view); - } - - return value; - }; - - function Writer() { - this.clearCache(); - } - - Writer.prototype.clearCache = function () { - this._cache = {}; - this._partialCache = {}; - }; - - Writer.prototype.compile = function (template, tags) { - var fn = this._cache[template]; - - if (!fn) { - var tokens = exports.parse(template, tags); - fn = this._cache[template] = this.compileTokens(tokens, template); - } - - return fn; - }; - - Writer.prototype.compilePartial = function (name, template, tags) { - var fn = this.compile(template, tags); - this._partialCache[name] = fn; - return fn; - }; - - Writer.prototype.compileTokens = function (tokens, template) { - var fn = compileTokens(tokens); - var self = this; - - return function (view, partials) { - if (partials) { - if (typeof partials === "function") { - self._loadPartial = partials; - } else { - for (var name in partials) { - self.compilePartial(name, partials[name]); - } - } - } - - return fn(self, Context.make(view), template); - }; - }; - - Writer.prototype.render = function (template, view, partials) { - return this.compile(template)(view, partials); - }; - - Writer.prototype._section = function (name, context, text, callback) { - var value = context.lookup(name); - - switch (typeof value) { - case "object": - if (isArray(value)) { - var buffer = ""; - - for (var i = 0, len = value.length; i < len; ++i) { - buffer += callback(this, context.push(value[i])); - } - - return buffer; - } - - return value ? callback(this, context.push(value)) : ""; - case "function": - var self = this; - var scopedRender = function (template) { - return self.render(template, context); - }; - - var result = value.call(context.view, text, scopedRender); - return result != null ? result : ""; - default: - if (value) { - return callback(this, context); - } - } - - return ""; - }; - - Writer.prototype._inverted = function (name, context, callback) { - var value = context.lookup(name); - - // Use JavaScript's definition of falsy. Include empty arrays. - // See https://github.com/janl/mustache.js/issues/186 - if (!value || (isArray(value) && value.length === 0)) { - return callback(this, context); - } - - return ""; - }; - - Writer.prototype._partial = function (name, context) { - if (!(name in this._partialCache) && this._loadPartial) { - this.compilePartial(name, this._loadPartial(name)); - } - - var fn = this._partialCache[name]; - - return fn ? fn(context) : ""; - }; - - Writer.prototype._name = function (name, context) { - var value = context.lookup(name); - - if (typeof value === "function") { - value = value.call(context.view); - } - - return (value == null) ? "" : String(value); - }; - - Writer.prototype._escaped = function (name, context) { - return exports.escape(this._name(name, context)); - }; - - /** - * Low-level function that compiles the given `tokens` into a function - * that accepts three arguments: a Writer, a Context, and the template. - */ - function compileTokens(tokens) { - var subRenders = {}; - - function subRender(i, tokens, template) { - if (!subRenders[i]) { - var fn = compileTokens(tokens); - subRenders[i] = function (writer, context) { - return fn(writer, context, template); - }; - } - - return subRenders[i]; - } - - return function (writer, context, template) { - var buffer = ""; - var token, sectionText; - - for (var i = 0, len = tokens.length; i < len; ++i) { - token = tokens[i]; - - switch (token[0]) { - case "#": - sectionText = template.slice(token[3], token[5]); - buffer += writer._section(token[1], context, sectionText, subRender(i, token[4], template)); - break; - case "^": - buffer += writer._inverted(token[1], context, subRender(i, token[4], template)); - break; - case ">": - buffer += writer._partial(token[1], context); - break; - case "&": - buffer += writer._name(token[1], context); - break; - case "name": - buffer += writer._escaped(token[1], context); - break; - case "text": - buffer += token[1]; - break; - } - } - - return buffer; - }; - } - - /** - * Forms the given array of `tokens` into a nested tree structure where - * tokens that represent a section have two additional items: 1) an array of - * all tokens that appear in that section and 2) the index in the original - * template that represents the end of that section. - */ - function nestTokens(tokens) { - var tree = []; - var collector = tree; - var sections = []; - - var token; - for (var i = 0, len = tokens.length; i < len; ++i) { - token = tokens[i]; - switch (token[0]) { - case '#': - case '^': - sections.push(token); - collector.push(token); - collector = token[4] = []; - break; - case '/': - var section = sections.pop(); - section[5] = token[2]; - collector = sections.length > 0 ? sections[sections.length - 1][4] : tree; - break; - default: - collector.push(token); - } - } - - return tree; - } - - /** - * Combines the values of consecutive text tokens in the given `tokens` array - * to a single token. - */ - function squashTokens(tokens) { - var squashedTokens = []; - - var token, lastToken; - for (var i = 0, len = tokens.length; i < len; ++i) { - token = tokens[i]; - if (token[0] === 'text' && lastToken && lastToken[0] === 'text') { - lastToken[1] += token[1]; - lastToken[3] = token[3]; - } else { - lastToken = token; - squashedTokens.push(token); - } - } - - return squashedTokens; - } - - function escapeTags(tags) { - return [ - new RegExp(escapeRe(tags[0]) + "\\s*"), - new RegExp("\\s*" + escapeRe(tags[1])) - ]; - } - - /** - * Breaks up the given `template` string into a tree of token objects. If - * `tags` is given here it must be an array with two string values: the - * opening and closing tags used in the template (e.g. ["<%", "%>"]). Of - * course, the default is to use mustaches (i.e. Mustache.tags). - */ - exports.parse = function (template, tags) { - template = template || ''; - tags = tags || exports.tags; - - if (typeof tags === 'string') tags = tags.split(spaceRe); - if (tags.length !== 2) { - throw new Error('Invalid tags: ' + tags.join(', ')); - } - - var tagRes = escapeTags(tags); - var scanner = new Scanner(template); - - var sections = []; // Stack to hold section tokens - var tokens = []; // Buffer to hold the tokens - var spaces = []; // Indices of whitespace tokens on the current line - var hasTag = false; // Is there a {{tag}} on the current line? - var nonSpace = false; // Is there a non-space char on the current line? - - // Strips all whitespace tokens array for the current line - // if there was a {{#tag}} on it and otherwise only space. - function stripSpace() { - if (hasTag && !nonSpace) { - while (spaces.length) { - tokens.splice(spaces.pop(), 1); - } - } else { - spaces = []; - } - - hasTag = false; - nonSpace = false; - } - - var start, type, value, chr; - while (!scanner.eos()) { - start = scanner.pos; - value = scanner.scanUntil(tagRes[0]); - - if (value) { - for (var i = 0, len = value.length; i < len; ++i) { - chr = value.charAt(i); - - if (isWhitespace(chr)) { - spaces.push(tokens.length); - } else { - nonSpace = true; - } - - tokens.push(["text", chr, start, start + 1]); - start += 1; - - if (chr === "\n") { - stripSpace(); // Check for whitespace on the current line. - } - } - } - - start = scanner.pos; - - // Match the opening tag. - if (!scanner.scan(tagRes[0])) { - break; - } - - hasTag = true; - type = scanner.scan(tagRe) || "name"; - - // Skip any whitespace between tag and value. - scanner.scan(whiteRe); - - // Extract the tag value. - if (type === "=") { - value = scanner.scanUntil(eqRe); - scanner.scan(eqRe); - scanner.scanUntil(tagRes[1]); - } else if (type === "{") { - var closeRe = new RegExp("\\s*" + escapeRe("}" + tags[1])); - value = scanner.scanUntil(closeRe); - scanner.scan(curlyRe); - scanner.scanUntil(tagRes[1]); - type = "&"; - } else { - value = scanner.scanUntil(tagRes[1]); - } - - // Match the closing tag. - if (!scanner.scan(tagRes[1])) { - throw new Error('Unclosed tag at ' + scanner.pos); - } - - // Check section nesting. - if (type === '/') { - if (sections.length === 0) { - throw new Error('Unopened section "' + value + '" at ' + start); - } - - var section = sections.pop(); - - if (section[1] !== value) { - throw new Error('Unclosed section "' + section[1] + '" at ' + start); - } - } - - var token = [type, value, start, scanner.pos]; - tokens.push(token); - - if (type === '#' || type === '^') { - sections.push(token); - } else if (type === "name" || type === "{" || type === "&") { - nonSpace = true; - } else if (type === "=") { - // Set the tags for the next time around. - tags = value.split(spaceRe); - - if (tags.length !== 2) { - throw new Error('Invalid tags at ' + start + ': ' + tags.join(', ')); - } - - tagRes = escapeTags(tags); - } - } - - // Make sure there are no open sections when we're done. - var section = sections.pop(); - if (section) { - throw new Error('Unclosed section "' + section[1] + '" at ' + scanner.pos); - } - - return nestTokens(squashTokens(tokens)); - }; - - // The high-level clearCache, compile, compilePartial, and render functions - // use this default writer. - var _writer = new Writer(); - - /** - * Clears all cached templates and partials in the default writer. - */ - exports.clearCache = function () { - return _writer.clearCache(); - }; - - /** - * Compiles the given `template` to a reusable function using the default - * writer. - */ - exports.compile = function (template, tags) { - return _writer.compile(template, tags); - }; - - /** - * Compiles the partial with the given `name` and `template` to a reusable - * function using the default writer. - */ - exports.compilePartial = function (name, template, tags) { - return _writer.compilePartial(name, template, tags); - }; - - /** - * Compiles the given array of tokens (the output of a parse) to a reusable - * function using the default writer. - */ - exports.compileTokens = function (tokens, template) { - return _writer.compileTokens(tokens, template); - }; - - /** - * Renders the `template` with the given `view` and `partials` using the - * default writer. - */ - exports.render = function (template, view, partials) { - return _writer.render(template, view, partials); - }; - - // This is here for backwards compatibility with 0.4.x. - exports.to_html = function (template, view, partials, send) { - var result = exports.render(template, view, partials); - - if (typeof send === "function") { - send(result); - } else { - return result; - } - }; - - return exports; - -}()))); +/*! + * mustache.js - Logic-less {{mustache}} templates with JavaScript + * http://github.com/janl/mustache.js + */ + +/*global define: false*/ + +(function (root, factory) { + if (typeof exports === "object" && exports) { + module.exports = factory; // CommonJS + } else if (typeof define === "function" && define.amd) { + define(factory); // AMD + } else { + root.Mustache = factory; // "; + } + + /** + * Enable delayed plugin activation. To be used with scb_init() + * + * @param string $plugin + * @param string|array $callback + * + * @return void + */ + public static function add_activation_hook( $plugin, $callback ) { + if ( defined( 'SCB_LOAD_MU' ) ) { + register_activation_hook( $plugin, $callback ); + } else { + add_action( 'scb_activation_' . plugin_basename( $plugin ), $callback ); + } + } + + /** + * Execute activation hook. + * For debugging. + * + * @param string $plugin + * + * @return void + */ + public static function do_activation( $plugin ) { + do_action( 'scb_activation_' . plugin_basename( $plugin ) ); + } + + /** + * Allows more than one uninstall hooks. + * Also prevents an UPDATE query on each page load. + * + * @param string $plugin + * @param string|array $callback + * + * @return void + */ + public static function add_uninstall_hook( $plugin, $callback ) { + if ( ! is_admin() ) { + return; + } + + register_uninstall_hook( $plugin, '__return_false' ); // dummy + + add_action( 'uninstall_' . plugin_basename( $plugin ), $callback ); + } + + /** + * Execute uninstall hook. + * For debugging. + * + * @param string $plugin + * + * @return void + */ + public static function do_uninstall( $plugin ) { + do_action( 'uninstall_' . plugin_basename( $plugin ) ); + } + + /** + * Get the current, full URL. + * + * @return string + */ + public static function get_current_url() { + return ( is_ssl() ? 'https://' : 'http://' ) . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']; + } + + /** + * Apply a function to each element of a ( nested ) array recursively. + * + * @param string|array $callback + * @param array $array + * + * @return array + */ + public static function array_map_recursive( $callback, $array ) { + array_walk_recursive( $array, array( __CLASS__, 'array_map_recursive_helper' ), $callback ); + + return $array; + } + + public static function array_map_recursive_helper( &$val, $key, $callback ) { + $val = call_user_func( $callback, $val ); + } + + /** + * Extract certain $keys from $array. + * + * @deprecated WP 3.1 + * @deprecated Use wp_array_slice_assoc() + * @see wp_array_slice_assoc() + * + * @param array $array + * @param array $keys + * + * @return array + */ + public static function array_extract( $array, $keys ) { + _deprecated_function( __CLASS__ . '::' . __FUNCTION__, 'WP 3.1', 'wp_array_slice_assoc()' ); + return wp_array_slice_assoc( $array, $keys ); + } + + /** + * Extract a certain value from a list of arrays. + * + * @deprecated WP 3.1 + * @deprecated Use wp_list_pluck() + * @see wp_list_pluck() + * + * @param array $array + * @param string $key + * + * @return array + */ + public static function array_pluck( $array, $key ) { + _deprecated_function( __CLASS__ . '::' . __FUNCTION__, 'WP 3.1', 'wp_list_pluck()' ); + return wp_list_pluck( $array, $key ); + } + + /** + * Transform a list of objects into an associative array. + * + * @deprecated r41 + * @deprecated Use scb_list_fold() + * @see scb_list_fold() + * + * @param array $objects + * @param string $key + * @param string $value + * + * @return array + */ + public static function objects_to_assoc( $objects, $key, $value ) { + _deprecated_function( __CLASS__ . '::' . __FUNCTION__, 'r41', 'scb_list_fold()' ); + return scb_list_fold( $objects, $key, $value ); + } + + /** + * Prepare an array for an IN statement. + * + * @param array $values + * + * @return string + */ + public static function array_to_sql( $values ) { + foreach ( $values as &$val ) { + $val = "'" . esc_sql( trim( $val ) ) . "'"; + } + + return implode( ',', $values ); + } + + /** + * Example: split_at( '' ) => array( '', '' ) + * + * @param string $delim + * @param string $str + * + * @return array + */ + public static function split_at( $delim, $str ) { + $i = strpos( $str, $delim ); + + if ( false === $i ) { + return false; + } + + $start = substr( $str, 0, $i ); + $finish = substr( $str, $i ); + + return array( $start, $finish ); + } +} + +/** + * Return a standard admin notice. + * + * @param string $msg + * @param string $class (optional) + * + * @return string + */ +function scb_admin_notice( $msg, $class = 'updated' ) { + return html( "div class='$class fade'", html( "p", $msg ) ); +} + +/** + * Transform a list of objects into an associative array. + * + * @param array $objects + * @param string $key + * @param string $value + * + * @return array + */ +function scb_list_fold( $list, $key, $value ) { + $r = array(); + + if ( is_array( reset( $list ) ) ) { + foreach ( $list as $item ) { + $r[ $item[ $key ] ] = $item[ $value ]; + } + } else { + foreach ( $list as $item ) { + $r[ $item->$key ] = $item->$value; + } + } + + return $r; +} + +/** + * Splits a list into sets, grouped by the result of running each value through $fn. + * + * @param array $list List of items to be partitioned. + * @param callback $fn Function that takes an element and returns a string key. + * + * @return array + */ +function scb_list_group_by( $list, $fn ) { + $groups = array(); + + foreach ( $list as $item ) { + $key = call_user_func( $fn, $item ); + + if ( null === $key ) { + continue; + } + + $groups[ $key ][] = $item; + } + + return $groups; +} + +//_____Database Table Utilities_____ + +/** + * Register a table with $wpdb. + * + * @param string $key The key to be used on the $wpdb object. + * @param string $name (optional) The actual name of the table, without $wpdb->prefix. + * + * @return void + */ +function scb_register_table( $key, $name = false ) { + global $wpdb; + + if ( ! $name ) { + $name = $key; + } + + $wpdb->tables[] = $name; + $wpdb->$key = $wpdb->prefix . $name; +} + +/** + * Runs the SQL query for installing/upgrading a table. + * + * @param string $key The key used in scb_register_table(). + * @param string $columns The SQL columns for the CREATE TABLE statement. + * @param array $opts (optional) Various other options. + * + * @return void + */ +function scb_install_table( $key, $columns, $opts = array() ) { + global $wpdb; + + $full_table_name = $wpdb->$key; + + if ( is_string( $opts ) ) { + $opts = array( 'upgrade_method' => $opts ); + } + + $opts = wp_parse_args( $opts, array( + 'upgrade_method' => 'dbDelta', + 'table_options' => '', + ) ); + + $charset_collate = ''; + if ( $wpdb->has_cap( 'collation' ) ) { + if ( ! empty( $wpdb->charset ) ) { + $charset_collate = "DEFAULT CHARACTER SET $wpdb->charset"; + } + if ( ! empty( $wpdb->collate ) ) { + $charset_collate .= " COLLATE $wpdb->collate"; + } + } + + $table_options = $charset_collate . ' ' . $opts['table_options']; + + if ( 'dbDelta' == $opts['upgrade_method'] ) { + require_once ABSPATH . 'wp-admin/includes/upgrade.php'; + dbDelta( "CREATE TABLE $full_table_name ( $columns ) $table_options" ); + return; + } + + if ( 'delete_first' == $opts['upgrade_method'] ) { + $wpdb->query( "DROP TABLE IF EXISTS $full_table_name;" ); + } + + $wpdb->query( "CREATE TABLE IF NOT EXISTS $full_table_name ( $columns ) $table_options;" ); +} + +/** + * Runs the SQL query for uninstalling a table. + * + * @param string $key The key used in scb_register_table(). + * + * @return void + */ +function scb_uninstall_table( $key ) { + global $wpdb; + + $wpdb->query( "DROP TABLE IF EXISTS " . $wpdb->$key ); +} + +//_____Minimalist HTML framework_____ + +/** + * Generate an HTML tag. Atributes are escaped. Content is NOT escaped. + * + * @param string $tag + * + * @return string + */ +if ( ! function_exists( 'html' ) ): +function html( $tag ) { + static $SELF_CLOSING_TAGS = array( 'area', 'base', 'basefont', 'br', 'hr', 'input', 'img', 'link', 'meta' ); + + $args = func_get_args(); + + $tag = array_shift( $args ); + + if ( is_array( $args[0] ) ) { + $closing = $tag; + $attributes = array_shift( $args ); + foreach ( $attributes as $key => $value ) { + if ( false === $value ) { + continue; + } + + if ( true === $value ) { + $value = $key; + } + + $tag .= ' ' . $key . '="' . esc_attr( $value ) . '"'; + } + } else { + list( $closing ) = explode( ' ', $tag, 2 ); + } + + if ( in_array( $closing, $SELF_CLOSING_TAGS ) ) { + return "<{$tag} />"; + } + + $content = implode( '', $args ); + + return "<{$tag}>{$content}"; +} +endif; + +/** + * Generate an tag. + * + * @param string $url + * @param string $title (optional) + * + * @return string + */ +if ( ! function_exists( 'html_link' ) ): +function html_link( $url, $title = '' ) { + if ( empty( $title ) ) { + $title = $url; + } + + return html( 'a', array( 'href' => esc_url( $url ) ), $title ); +} +endif; + +/** + * Returns an array of query flags. + * + * @param object $wp_query (optional) + * + * @return array + */ +function scb_get_query_flags( $wp_query = null ) { + if ( ! $wp_query ) { + $wp_query = $GLOBALS['wp_query']; + } + + $flags = array(); + foreach ( get_object_vars( $wp_query ) as $key => $val ) { + if ( 'is_' == substr( $key, 0, 3 ) && $val ) { + $flags[] = substr( $key, 3 ); + } + } + + return $flags; +} + +//_____Compatibility layer_____ + +/** + * Update data from a post field based on Post ID. + * @see https://core.trac.wordpress.org/ticket/10946 + * + * @param string $field Post field name. + * @param string $value Post field value. + * @param int $post_id Post ID. + * + * @return bool Result of UPDATE query. + */ +if ( ! function_exists( 'set_post_field' ) ) : +function set_post_field( $field, $value, $post_id ) { + global $wpdb; + + $post_id = absint( $post_id ); + $value = sanitize_post_field( $field, $value, $post_id, 'db' ); + + return $wpdb->update( $wpdb->posts, array( $field => $value ), array( 'ID' => $post_id ) ); +} +endif; + diff --git a/vendor/scribu/scb-framework/Widget.php b/vendor/scribu/scb-framework/Widget.php new file mode 100644 index 00000000..5a73ee41 --- /dev/null +++ b/vendor/scribu/scb-framework/Widget.php @@ -0,0 +1,124 @@ +defaults ); + + extract( $args ); + + echo $before_widget; + + $title = apply_filters( 'widget_title', isset( $instance['title'] ) ? $instance['title'] : '', $instance, $this->id_base ); + + if ( ! empty( $title ) ) { + echo $before_title . $title . $after_title; + } + + $this->content( $instance ); + + echo $after_widget; + } + + /** + * This is where the actual widget content goes. + * + * @param array $instance The settings for the particular instance of the widget. + * + * @return void + */ + protected function content( $instance ) { } + + +//_____HELPER METHODS_____ + + + /** + * Generates a input form field. + * + * @param array $args + * @param array $formdata (optional) + * + * @return string + */ + protected function input( $args, $formdata = array() ) { + $prefix = array( 'widget-' . $this->id_base, $this->number ); + + $form = new scbForm( $formdata, $prefix ); + + // Add default class + if ( ! isset( $args['extra'] ) && 'text' == $args['type'] ) { + $args['extra'] = array( 'class' => 'widefat' ); + } + + // Add default label position + if ( ! in_array( $args['type'], array( 'checkbox', 'radio' ) ) && empty( $args['desc_pos'] ) ) { + $args['desc_pos'] = 'before'; + } + + $name = $args['name']; + + if ( ! is_array( $name ) && '[]' == substr( $name, -2 ) ) { + $name = array( substr( $name, 0, -2 ), '' ); + } + + $args['name'] = $name; + + return $form->input( $args ); + } + +} + diff --git a/vendor/scribu/scb-framework/composer.json b/vendor/scribu/scb-framework/composer.json new file mode 100644 index 00000000..0387fefc --- /dev/null +++ b/vendor/scribu/scb-framework/composer.json @@ -0,0 +1,22 @@ +{ + "name" : "scribu/scb-framework", + "description": "A set of useful classes for faster plugin development", + "keywords" : ["wordpress"], + "homepage" : "https://github.com/scribu/wp-scb-framework", + "license" : "GPL-3.0+", + "authors" : [ + { + "name" : "Cristi Burcă", + "homepage": "http://scribu.net/" + } + ], + "support" : { + "issues": "https://github.com/scribu/wp-scb-framework/issues", + "source": "https://github.com/scribu/wp-scb-framework", + "wiki": "https://github.com/scribu/wp-scb-framework/wiki" + }, + "autoload" : { + "classmap": ["."], + "files" : ["load-composer.php", "Util.php"] + } +} diff --git a/vendor/scribu/scb-framework/load-composer.php b/vendor/scribu/scb-framework/load-composer.php new file mode 100644 index 00000000..98c8c917 --- /dev/null +++ b/vendor/scribu/scb-framework/load-composer.php @@ -0,0 +1,12 @@ + $callback ) { + if ( dirname( dirname( plugin_basename( $file ) ) ) == $plugin_dir ) { + self::load( false ); + call_user_func( $callback ); + do_action( 'scb_activation_' . $plugin ); + break; + } + } + } + + public static function load( $do_callbacks = true ) { + arsort( self::$candidates ); + + $file = key( self::$candidates ); + + $path = dirname( $file ) . '/'; + + foreach ( self::$classes[ $file ] as $class_name ) { + if ( class_exists( $class_name ) ) { + continue; + } + + $fpath = $path . substr( $class_name, 3 ) . '.php'; + if ( file_exists( $fpath ) ) { + include $fpath; + self::$loaded[] = $fpath; + } + } + + if ( $do_callbacks ) { + foreach ( self::$callbacks as $callback ) { + call_user_func( $callback ); + } + } + } + + static function get_info() { + arsort( self::$candidates ); + + return array( self::$loaded, self::$candidates ); + } +} +endif; + +if ( ! function_exists( 'scb_init' ) ) : +function scb_init( $callback = '' ) { + scbLoad4::init( $callback ); +} +endif; +