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 ''; + //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 '
'; + + //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 '
'; + print_r( ( $connection )); + echo ''; + + + echo '
'; + echo $connection->p2p_type; + 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 = '