Event Tickets - Version 4.1

Version Description

Download this release

Release Info

Developer borkweb
Plugin Icon 128x128 Event Tickets
Version 4.1
Comparing to
See all releases

Code changes from version 4.0.6 to 4.1

Files changed (98) hide show
  1. common/README.md +0 -2
  2. common/src/Tribe/Abstract_Deactivation.php +0 -0
  3. common/src/Tribe/Admin/Help_Page.php +2 -1
  4. common/src/Tribe/Admin/Notice/Archive_Slug_Conflict.php +30 -2
  5. common/src/Tribe/App_Shop.php +0 -0
  6. common/src/Tribe/Asset/Factory.php +0 -0
  7. common/src/Tribe/Autoloader.php +0 -0
  8. common/src/Tribe/Cache.php +0 -0
  9. common/src/Tribe/Cache_Listener.php +0 -0
  10. common/src/Tribe/Changelog_Reader.php +0 -0
  11. common/src/Tribe/Credits.php +1 -1
  12. common/src/Tribe/Date_Utils.php +0 -0
  13. common/src/Tribe/Field.php +0 -0
  14. common/src/Tribe/Main.php +10 -1
  15. common/src/Tribe/PUE/Checker.php +2 -2
  16. common/src/Tribe/PUE/Plugin_Info.php +0 -0
  17. common/src/Tribe/PUE/Utility.php +0 -0
  18. common/src/Tribe/Post_Transient.php +210 -0
  19. common/src/Tribe/Settings.php +0 -0
  20. common/src/Tribe/Settings_Tab.php +0 -0
  21. common/src/Tribe/Support.php +31 -4
  22. common/src/Tribe/Support/Obfuscator.php +68 -0
  23. common/src/Tribe/Support/Template_Checker.php +276 -0
  24. common/src/Tribe/Support/Template_Checker_Report.php +119 -0
  25. common/src/Tribe/Template_Factory.php +0 -0
  26. common/src/Tribe/Template_Part_Cache.php +0 -0
  27. common/src/Tribe/Validate.php +0 -0
  28. common/src/Tribe/View_Helpers.php +0 -0
  29. common/src/admin-views/tribe-options-display.php +0 -0
  30. common/src/admin-views/tribe-options-general.php +0 -0
  31. common/src/admin-views/tribe-options-help.php +3 -0
  32. common/src/admin-views/tribe-options-licenses.php +0 -0
  33. common/src/admin-views/tribe-options-network.php +0 -0
  34. common/src/functions/template-tags/date.php +0 -0
  35. common/src/functions/template-tags/general.php +1 -2
  36. common/src/resources/css/tribe-common-admin.css +9 -8
  37. common/src/resources/js/notice-dismiss.js +27 -0
  38. common/tests.md +0 -47
  39. common/tests/wpunit/Tribe/Events/common/Date_UtilsTest.php +0 -228
  40. common/tribe-autoload.php +4 -3
  41. common/tribe-common.php +1 -1
  42. common/vendor/jquery/images/ui-bg_flat_0_aaaaaa_40x100.png +0 -0
  43. common/vendor/jquery/images/ui-bg_flat_75_ffffff_40x100.png +0 -0
  44. common/vendor/jquery/images/ui-bg_glass_55_fbf9ee_1x400.png +0 -0
  45. common/vendor/jquery/images/ui-bg_glass_65_ffffff_1x400.png +0 -0
  46. common/vendor/jquery/images/ui-bg_glass_75_dadada_1x400.png +0 -0
  47. common/vendor/jquery/images/ui-bg_glass_75_e6e6e6_1x400.png +0 -0
  48. common/vendor/jquery/images/ui-bg_glass_95_fef1ec_1x400.png +0 -0
  49. common/vendor/jquery/images/ui-bg_highlight-soft_75_cccccc_1x100.png +0 -0
  50. common/vendor/jquery/images/ui-icons_222222_256x240.png +0 -0
  51. common/vendor/jquery/images/ui-icons_2e83ff_256x240.png +0 -0
  52. common/vendor/jquery/images/ui-icons_454545_256x240.png +0 -0
  53. common/vendor/jquery/images/ui-icons_888888_256x240.png +0 -0
  54. common/vendor/jquery/images/ui-icons_cd0a0a_256x240.png +0 -0
  55. common/vendor/jquery/ui.theme.css +0 -0
  56. event-tickets.php +1 -1
  57. lang/event-tickets-es_ES.mo +0 -0
  58. lang/event-tickets-es_ES.po +4 -4
  59. readme.txt +13 -3
  60. src/Tribe/Attendees_Table.php +79 -9
  61. src/Tribe/Global_Stock.php +126 -0
  62. src/Tribe/Google_Event_Data.php +0 -0
  63. src/Tribe/Main.php +37 -12
  64. src/Tribe/Metabox.php +4 -0
  65. src/Tribe/RSVP.php +99 -13
  66. src/Tribe/Ticket_Object.php +99 -1
  67. src/Tribe/Tickets.php +204 -2
  68. src/Tribe/Tickets_Handler.php +70 -43
  69. src/admin-views/attendees.php +10 -2
  70. src/admin-views/list.php +15 -24
  71. src/admin-views/meta-box.php +59 -4
  72. src/admin-views/rsvp-metabox-advanced.php +1 -1
  73. src/deprecated/TribeEventsTicketObject.php +0 -0
  74. src/deprecated/TribeEventsTickets.php +0 -0
  75. src/deprecated/TribeEventsTicketsAttendeesTable.php +0 -0
  76. src/deprecated/TribeEventsTicketsMetabox.php +0 -0
  77. src/deprecated/TribeEventsTicketsPro.php +0 -0
  78. src/deprecated/Tribe__Events__Tickets__Attendees_Table.php +0 -0
  79. src/deprecated/Tribe__Events__Tickets__Metabox.php +0 -0
  80. src/deprecated/Tribe__Events__Tickets__Ticket_Object.php +0 -0
  81. src/deprecated/Tribe__Events__Tickets__Tickets.php +0 -0
  82. src/deprecated/Tribe__Events__Tickets__Tickets_Pro.php +0 -0
  83. src/resources/css/rsvp.css +6 -2
  84. src/resources/css/rsvp.min.css +1 -0
  85. src/resources/css/tickets-attendees-print.min.css +1 -1
  86. src/resources/css/tickets-attendees.min.css +1 -1
  87. src/resources/css/tickets.css +20 -0
  88. src/resources/css/tickets.min.css +1 -1
  89. src/resources/js/frontend-ticket-form.js +290 -0
  90. src/resources/js/frontend-ticket-form.min.js +1 -0
  91. src/resources/js/rsvp.js +29 -0
  92. src/resources/js/rsvp.min.js +1 -1
  93. src/resources/js/tickets-attendees.min.js +1 -1
  94. src/resources/js/tickets.js +158 -19
  95. src/resources/js/tickets.min.js +1 -1
  96. src/template-tags/tickets.php +42 -13
  97. src/views/tickets/rsvp.php +43 -15
  98. tests.md +0 -47
common/README.md DELETED
@@ -1,2 +0,0 @@
1
- # tribe-common
2
- Common classes and functions used in our plugins
 
 
common/src/Tribe/Abstract_Deactivation.php CHANGED
File without changes
common/src/Tribe/Admin/Help_Page.php CHANGED
@@ -430,7 +430,8 @@ class Tribe__Admin__Help_Page {
430
  'i' => array( 'style' => array(), 'class' => array(), 'id' => array() ),
431
  'u' => array( 'style' => array(), 'class' => array(), 'id' => array() ),
432
 
433
- 'div' => array( 'style' => array(), 'class' => array(), 'id' => array() ),
 
434
  'p' => array( 'style' => array(), 'class' => array(), 'id' => array() ),
435
  'ol' => array( 'style' => array(), 'class' => array(), 'id' => array() ),
436
  'ul' => array( 'style' => array(), 'class' => array(), 'id' => array() ),
430
  'i' => array( 'style' => array(), 'class' => array(), 'id' => array() ),
431
  'u' => array( 'style' => array(), 'class' => array(), 'id' => array() ),
432
 
433
+ 'div' => array( 'style' => array(), 'class' => array(), 'id' => array() ),
434
+ 'h5' => array( 'style' => array(), 'class' => array(), 'id' => array() ),
435
  'p' => array( 'style' => array(), 'class' => array(), 'id' => array() ),
436
  'ol' => array( 'style' => array(), 'class' => array(), 'id' => array() ),
437
  'ul' => array( 'style' => array(), 'class' => array(), 'id' => array() ),
common/src/Tribe/Admin/Notice/Archive_Slug_Conflict.php CHANGED
@@ -44,15 +44,43 @@ class Tribe__Admin__Notice__Archive_Slug_Conflict {
44
  return;
45
  }
46
  $this->page = $page;
 
 
 
 
 
47
  add_action( 'admin_notices', array( $this, 'notice' ) );
48
  }
49
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
50
  /**
51
  * Echoes the admin notice to the page
52
  */
53
  public function notice() {
54
  // What's happening?
55
- $page_title = apply_filters( 'the_title', $this->page->post_title );
56
  $line_1 = __( sprintf( 'The page "%1$s" uses the "/%2$s" slug: the Events Calendar plugin will show its calendar in place of the page.', $page_title, $this->archive_slug ), 'tribe-common' );
57
 
58
  // What the user can do
@@ -68,6 +96,6 @@ class Tribe__Admin__Notice__Archive_Slug_Conflict {
68
 
69
  $line_2 = __( sprintf( '%1$s or %2$s', $page_edit_link_string, $events_settings_link_string ), 'tribe-common' );
70
 
71
- echo sprintf( '<div id="message" class="error"><p>%s</p><p>%s</p></div>', $line_1, $line_2 );
72
  }
73
  }
44
  return;
45
  }
46
  $this->page = $page;
47
+ $dimissed_notices = get_user_meta( get_current_user_id(), 'tribe-dismiss-notice' );
48
+
49
+ if ( in_array( 'archive-slug-conflict', $dimissed_notices ) ) {
50
+ return;
51
+ }
52
  add_action( 'admin_notices', array( $this, 'notice' ) );
53
  }
54
 
55
+ /**
56
+ * Hooked before maybe_add_admin_notice to prevent a notice to show it has been dimissed
57
+ * @return void
58
+ */
59
+ public function maybe_dismiss() {
60
+ if ( empty( $_GET['tribe-dismiss-notice'] ) ) {
61
+ return;
62
+ }
63
+
64
+ $notice = esc_attr( $_GET['tribe-dismiss-notice'] );
65
+
66
+ if ( 'archive-slug-conflict' !== $notice ) {
67
+ return;
68
+ }
69
+
70
+ $dimissed_notices = get_user_meta( get_current_user_id(), 'tribe-dismiss-notice' );
71
+ if ( in_array( 'archive-slug-conflict', $dimissed_notices ) ) {
72
+ return;
73
+ }
74
+
75
+ add_user_meta( get_current_user_id(), 'tribe-dismiss-notice', 'archive-slug-conflict', false );
76
+ }
77
+
78
  /**
79
  * Echoes the admin notice to the page
80
  */
81
  public function notice() {
82
  // What's happening?
83
+ $page_title = apply_filters( 'the_title', $this->page->post_title, $this->page->ID );
84
  $line_1 = __( sprintf( 'The page "%1$s" uses the "/%2$s" slug: the Events Calendar plugin will show its calendar in place of the page.', $page_title, $this->archive_slug ), 'tribe-common' );
85
 
86
  // What the user can do
96
 
97
  $line_2 = __( sprintf( '%1$s or %2$s', $page_edit_link_string, $events_settings_link_string ), 'tribe-common' );
98
 
99
+ echo sprintf( '<div id="message" class="notice error is-dismissible tribe-dismiss-notice" data-ref="archive-slug-conflict"><p>%s</p><p>%s</p></div>', $line_1, $line_2 );
100
  }
101
  }
common/src/Tribe/App_Shop.php CHANGED
File without changes
common/src/Tribe/Asset/Factory.php CHANGED
File without changes
common/src/Tribe/Autoloader.php CHANGED
File without changes
common/src/Tribe/Cache.php CHANGED
File without changes
common/src/Tribe/Cache_Listener.php CHANGED
File without changes
common/src/Tribe/Changelog_Reader.php CHANGED
File without changes
common/src/Tribe/Credits.php CHANGED
@@ -23,7 +23,7 @@ class Tribe__Credits {
23
  * @return void
24
  **/
25
  public function html_comment_credit( $after_html ) {
26
- $html_credit = "\n<!--\n" . esc_html__( 'This calendar is powered by The Events Calendar.', 'tribe-common' ) . "\nhttp://eventscalendarpro.com/\n-->\n";
27
  $after_html .= apply_filters( 'tribe_html_credit', $html_credit );
28
  return $after_html;
29
  }
23
  * @return void
24
  **/
25
  public function html_comment_credit( $after_html ) {
26
+ $html_credit = "\n<!--\n" . esc_html__( 'This calendar is powered by The Events Calendar.', 'tribe-common' ) . "\nhttp://m.tri.be/18wn\n-->\n";
27
  $after_html .= apply_filters( 'tribe_html_credit', $html_credit );
28
  return $after_html;
29
  }
common/src/Tribe/Date_Utils.php CHANGED
File without changes
common/src/Tribe/Field.php CHANGED
File without changes
common/src/Tribe/Main.php CHANGED
@@ -17,7 +17,7 @@ class Tribe__Main {
17
  const OPTIONNAME = 'tribe_events_calendar_options';
18
  const OPTIONNAMENETWORK = 'tribe_events_calendar_network_options';
19
 
20
- const VERSION = '4.0.6';
21
  const FEED_URL = 'https://theeventscalendar.com/feed/';
22
 
23
  protected $plugin_context;
@@ -123,6 +123,14 @@ class Tribe__Main {
123
  apply_filters( 'tribe_events_css_version', self::VERSION ),
124
  true
125
  );
 
 
 
 
 
 
 
 
126
  }
127
 
128
  /**
@@ -173,6 +181,7 @@ class Tribe__Main {
173
 
174
  public function admin_enqueue_scripts() {
175
  wp_enqueue_script( 'tribe-inline-bumpdown' );
 
176
  wp_enqueue_style( 'tribe-common-admin' );
177
 
178
  $helper = Tribe__Admin__Helpers::instance();
17
  const OPTIONNAME = 'tribe_events_calendar_options';
18
  const OPTIONNAMENETWORK = 'tribe_events_calendar_network_options';
19
 
20
+ const VERSION = '4.1';
21
  const FEED_URL = 'https://theeventscalendar.com/feed/';
22
 
23
  protected $plugin_context;
123
  apply_filters( 'tribe_events_css_version', self::VERSION ),
124
  true
125
  );
126
+
127
+ wp_register_script(
128
+ 'tribe-notice-dismiss',
129
+ $resources_url . '/js/notice-dismiss.js',
130
+ array( 'jquery' ),
131
+ apply_filters( 'tribe_events_css_version', self::VERSION ),
132
+ true
133
+ );
134
  }
135
 
136
  /**
181
 
182
  public function admin_enqueue_scripts() {
183
  wp_enqueue_script( 'tribe-inline-bumpdown' );
184
+ wp_enqueue_script( 'tribe-notice-dismiss' );
185
  wp_enqueue_style( 'tribe-common-admin' );
186
 
187
  $helper = Tribe__Admin__Helpers::instance();
common/src/Tribe/PUE/Checker.php CHANGED
@@ -430,12 +430,12 @@ if ( ! class_exists( 'Tribe__PUE__Checker' ) ) {
430
  } elseif ( isset( $pluginInfo->api_invalid ) && $pluginInfo->api_invalid == 1 ) {
431
  $response['message'] = esc_html__( 'Sorry, this key is not valid.', 'tribe-common' );
432
  } else {
433
- $api_secret_key = tribe_get_option( $this->pue_install_key );
434
  if ( $api_secret_key && $api_secret_key === $queryArgs['pu_install_key'] ){
435
  $default_success_msg = sprintf( esc_html__( 'Valid Key! Expires on %s', 'tribe-common' ), $expiration );
436
  } else {
437
  // Set the key
438
- tribe_update_option( $this->pue_install_key, $queryArgs['pu_install_key'] );
439
 
440
  $default_success_msg = sprintf( esc_html__( 'Thanks for setting up a valid key, it will expire on %s', 'tribe-common' ), $expiration );
441
  }
430
  } elseif ( isset( $pluginInfo->api_invalid ) && $pluginInfo->api_invalid == 1 ) {
431
  $response['message'] = esc_html__( 'Sorry, this key is not valid.', 'tribe-common' );
432
  } else {
433
+ $api_secret_key = get_option( $this->pue_install_key );
434
  if ( $api_secret_key && $api_secret_key === $queryArgs['pu_install_key'] ){
435
  $default_success_msg = sprintf( esc_html__( 'Valid Key! Expires on %s', 'tribe-common' ), $expiration );
436
  } else {
437
  // Set the key
438
+ update_option( $this->pue_install_key, $queryArgs['pu_install_key'] );
439
 
440
  $default_success_msg = sprintf( esc_html__( 'Thanks for setting up a valid key, it will expire on %s', 'tribe-common' ), $expiration );
441
  }
common/src/Tribe/PUE/Plugin_Info.php CHANGED
File without changes
common/src/Tribe/PUE/Utility.php CHANGED
File without changes
common/src/Tribe/Post_Transient.php ADDED
@@ -0,0 +1,210 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * A Class to handle Transients for posts, useful for caching complex structures
5
+ * It uses the same logic as WordPress Transient, but instead of options it will
6
+ * use the Post Meta as the table
7
+ *
8
+ * @since 4.1
9
+ */
10
+ class Tribe__Post_Transient {
11
+
12
+ /**
13
+ * Get (and instantiate, if necessary) the instance of the class
14
+ *
15
+ * @since 4.1
16
+ * @static
17
+ * @return self
18
+ *
19
+ */
20
+ public static function instance() {
21
+ static $instance;
22
+
23
+ if ( ! $instance instanceof self ) {
24
+ $instance = new self;
25
+ }
26
+
27
+ return $instance;
28
+ }
29
+
30
+ /**
31
+ * Delete a post meta transient.
32
+ *
33
+ * @since 4.1
34
+ *
35
+ * @param int $post_id The Post ID, can also be a WP_Post
36
+ * @param string $transient Post Meta to Delete
37
+ * @param string $value Only delete if the value Matches
38
+ *
39
+ */
40
+ public function delete( $post_id, $transient, $value = null ) {
41
+ global $_wp_using_ext_object_cache;
42
+
43
+ if ( is_numeric( $post_id ) ) {
44
+ $post_id = (int) $post_id;
45
+ } else {
46
+ $post = get_post( $post_id );
47
+ $post_id = $post->ID;
48
+ }
49
+
50
+ /**
51
+ * Use this to pre attach an action to deleting a Post Transient
52
+ *
53
+ * @since 4.1
54
+ *
55
+ * @param int $post_id Post ID
56
+ * @param string $transient The Post Meta Key
57
+ */
58
+ do_action( 'tribe_delete_post_meta_transient_' . $transient, $post_id, $transient );
59
+
60
+ if ( $_wp_using_ext_object_cache ) {
61
+ $result = wp_cache_delete( "tribe_{$transient}-{$post_id}", "tribe_post_meta_transient-{$post_id}" );
62
+ } else {
63
+ $meta_timeout = '_transient_timeout_' . $transient;
64
+ $meta = '_transient_' . $transient;
65
+ $result = delete_post_meta( $post_id, $meta, $value );
66
+ if ( $result ) {
67
+ delete_post_meta( $post_id, $meta_timeout, $value );
68
+ }
69
+ }
70
+
71
+ if ( $result ) {
72
+ /**
73
+ * Use this to attach an Action to when the Transient is deleted
74
+ *
75
+ * @since 4.1
76
+ *
77
+ * @param int $post_id Post ID
78
+ * @param string $transient The Post Meta Key
79
+ */
80
+ do_action( 'tribe_deleted_post_meta_transient', $transient, $post_id, $transient );
81
+ }
82
+
83
+ return $result;
84
+ }
85
+
86
+ /**
87
+ * Fetches the Transient Data
88
+ *
89
+ * @since 4.1
90
+ *
91
+ * @param int $post_id The Post ID, can also be a WP_Post
92
+ * @param string $transient Post Meta to Fetch
93
+ *
94
+ */
95
+ public function get( $post_id, $transient ) {
96
+ global $_wp_using_ext_object_cache;
97
+
98
+ if ( is_numeric( $post_id ) ) {
99
+ $post_id = (int) $post_id;
100
+ } else {
101
+ $post = get_post( $post_id );
102
+ $post_id = $post->ID;
103
+ }
104
+
105
+ if ( has_filter( 'tribe_pre_post_meta_transient_' . $transient ) ) {
106
+ /**
107
+ * Attach an action before getting the new Transient
108
+ *
109
+ * @since 4.1
110
+ *
111
+ * @param int $post_id Post ID
112
+ * @param string $transient The Post Meta Key
113
+ */
114
+ $pre = apply_filters( 'tribe_pre_post_meta_transient_' . $transient, $post_id, $transient );
115
+ if ( false !== $pre ) {
116
+ return $pre;
117
+ }
118
+ }
119
+
120
+ if ( $_wp_using_ext_object_cache ) {
121
+ $value = wp_cache_get( "tribe_{$transient}-{$post_id}", "tribe_post_meta_transient-{$post_id}" );
122
+ } else {
123
+ $meta_timeout = '_transient_timeout_' . $transient;
124
+ $meta = '_transient_' . $transient;
125
+ $value = get_post_meta( $post_id, $meta, true );
126
+ if ( $value && ! defined( 'WP_INSTALLING' ) ) {
127
+ if ( get_post_meta( $post_id, $meta_timeout, true ) < time() ) {
128
+ $this->delete( $post_id, $transient );
129
+ return false;
130
+ }
131
+ }
132
+ }
133
+
134
+ /**
135
+ * Attach an action after getting the new Transient
136
+ *
137
+ * @since 4.1
138
+ *
139
+ * @param int $post_id Post ID
140
+ * @param string $transient The Post Meta Key
141
+ */
142
+ return
143
+ has_filter( 'tribe_post_meta_transient_' . $transient )
144
+ ? apply_filters( 'tribe_post_meta_transient_' . $transient, $value, $post_id )
145
+ : $value;
146
+ }
147
+
148
+ /**
149
+ * Sets a new value for the Transient
150
+ *
151
+ * @since 4.1
152
+ *
153
+ * @param int $post_id The Post ID, can also be a WP_Post
154
+ * @param string $transient Post Meta to set
155
+ * @param string $value Only delete if the value Matches
156
+ * @param int $expiration How long this transient will be valid, in seconds
157
+ *
158
+ */
159
+ public function set( $post_id, $transient, $value, $expiration = 0 ) {
160
+ global $_wp_using_ext_object_cache;
161
+
162
+ if ( is_numeric( $post_id ) ) {
163
+ $post_id = (int) $post_id;
164
+ } else {
165
+ $post = get_post( $post_id );
166
+ $post_id = $post->ID;
167
+ }
168
+
169
+ $this->delete( $post_id, $transient );
170
+
171
+ /**
172
+ * Attach an action before setting the new Transient
173
+ *
174
+ * @since 4.1
175
+ *
176
+ * @param int $post_id Post ID
177
+ * @param string $transient The Post Meta Key
178
+ */
179
+ if ( has_filter( 'tribe_pre_set_post_meta_transient_' . $transient ) ) {
180
+ $value = apply_filters( 'tribe_pre_set_post_meta_transient_' . $transient, $value, $post_id, $transient );
181
+ }
182
+
183
+ if ( $_wp_using_ext_object_cache ) {
184
+ $result = wp_cache_set( "tribe_{$transient}-{$post_id}", $value, "tribe_post_meta_transient-{$post_id}", $expiration );
185
+ } else {
186
+ $meta_timeout = '_transient_timeout_' . $transient;
187
+ $meta = '_transient_' . $transient;
188
+ if ( $expiration ) {
189
+ add_post_meta( $post_id, $meta_timeout, time() + $expiration, true );
190
+ }
191
+ $result = add_post_meta( $post_id, $meta, $value, true );
192
+ }
193
+
194
+ if ( $result ) {
195
+ /**
196
+ * Attach an action after setting the new Transient
197
+ *
198
+ * @since 4.1
199
+ *
200
+ * @param int $post_id Post ID
201
+ * @param string $transient The Post Meta Key
202
+ */
203
+ do_action( 'tribe_set_post_meta_transient_' . $transient, $post_id, $transient );
204
+ }
205
+
206
+ return $result;
207
+ }
208
+
209
+
210
+ }
common/src/Tribe/Settings.php CHANGED
File without changes
common/src/Tribe/Settings_Tab.php CHANGED
File without changes
common/src/Tribe/Support.php CHANGED
@@ -15,6 +15,11 @@ if ( ! class_exists( 'Tribe__Support' ) ) {
15
  public static $support;
16
  public $rewrite_rules_purged = false;
17
 
 
 
 
 
 
18
  /**
19
  * Fields listed here contain HTML and should be escaped before being
20
  * printed.
@@ -26,6 +31,15 @@ if ( ! class_exists( 'Tribe__Support' ) ) {
26
  'tribeEventsBeforeHTML',
27
  );
28
 
 
 
 
 
 
 
 
 
 
29
  private function __construct() {
30
  $this->must_escape = (array) apply_filters( 'tribe_help_must_escape_fields', $this->must_escape );
31
  add_action( 'tribe_help_pre_get_sections', array( $this, 'append_system_info' ), 10 );
@@ -107,7 +121,8 @@ if ( ! class_exists( 'Tribe__Support' ) ) {
107
  $keys = apply_filters( 'tribe-pue-install-keys', array() );
108
 
109
  $systeminfo = array(
110
- 'url' => 'http://' . $_SERVER['HTTP_HOST'],
 
111
  'name' => $user->display_name,
112
  'email' => $user->user_email,
113
  'install keys' => $keys,
@@ -178,6 +193,9 @@ if ( ! class_exists( 'Tribe__Support' ) ) {
178
  if ( in_array( $obj_key, $this->must_escape ) ) {
179
  $obj_val = esc_html( $obj_val );
180
  }
 
 
 
181
  if ( is_array( $obj_val ) ) {
182
  $formatted_v[] = sprintf( '<li>%s = <pre>%s</pre></li>', $obj_key, print_r( $obj_val, true ) );
183
  } else {
@@ -200,6 +218,15 @@ if ( ! class_exists( 'Tribe__Support' ) ) {
200
  $this->rewrite_rules_purged = true;
201
  }//end log_rewrite_rule_purge
202
 
 
 
 
 
 
 
 
 
 
203
  /****************** SINGLETON GUTS ******************/
204
 
205
  /**
@@ -210,13 +237,13 @@ if ( ! class_exists( 'Tribe__Support' ) ) {
210
 
211
  public static function getInstance() {
212
  if ( null == self::$instance ) {
213
- $className = __CLASS__;
214
- self::$instance = new $className;
 
215
  }
216
 
217
  return self::$instance;
218
  }
219
-
220
  }
221
 
222
  }
15
  public static $support;
16
  public $rewrite_rules_purged = false;
17
 
18
+ /**
19
+ * @var Tribe__Support__Obfuscator
20
+ */
21
+ protected $obfuscator;
22
+
23
  /**
24
  * Fields listed here contain HTML and should be escaped before being
25
  * printed.
31
  'tribeEventsBeforeHTML',
32
  );
33
 
34
+ /**
35
+ * Field prefixes here should be partially obfuscated before being printed.
36
+ *
37
+ * @var array
38
+ */
39
+ protected $must_obfuscate_prefixes = array(
40
+ 'pue_install_key_',
41
+ );
42
+
43
  private function __construct() {
44
  $this->must_escape = (array) apply_filters( 'tribe_help_must_escape_fields', $this->must_escape );
45
  add_action( 'tribe_help_pre_get_sections', array( $this, 'append_system_info' ), 10 );
121
  $keys = apply_filters( 'tribe-pue-install-keys', array() );
122
 
123
  $systeminfo = array(
124
+ 'Home URL' => get_home_url(),
125
+ 'Site URL' => get_site_url(),
126
  'name' => $user->display_name,
127
  'email' => $user->user_email,
128
  'install keys' => $keys,
193
  if ( in_array( $obj_key, $this->must_escape ) ) {
194
  $obj_val = esc_html( $obj_val );
195
  }
196
+
197
+ $obj_val = $this->obfuscator->obfuscate( $obj_key, $obj_val );
198
+
199
  if ( is_array( $obj_val ) ) {
200
  $formatted_v[] = sprintf( '<li>%s = <pre>%s</pre></li>', $obj_key, print_r( $obj_val, true ) );
201
  } else {
218
  $this->rewrite_rules_purged = true;
219
  }//end log_rewrite_rule_purge
220
 
221
+ /**
222
+ * Sets the obfuscator to be used.
223
+ *
224
+ * @param Tribe__Support__Obfuscator $obfuscator
225
+ */
226
+ public function set_obfuscator( Tribe__Support__Obfuscator $obfuscator ) {
227
+ $this->obfuscator = $obfuscator;
228
+ }
229
+
230
  /****************** SINGLETON GUTS ******************/
231
 
232
  /**
237
 
238
  public static function getInstance() {
239
  if ( null == self::$instance ) {
240
+ $instance = new self;
241
+ $instance->set_obfuscator( new Tribe__Support__Obfuscator( $instance->must_obfuscate_prefixes ) );
242
+ self::$instance = $instance;
243
  }
244
 
245
  return self::$instance;
246
  }
 
247
  }
248
 
249
  }
common/src/Tribe/Support/Obfuscator.php ADDED
@@ -0,0 +1,68 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+
4
+ class Tribe__Support__Obfuscator {
5
+
6
+ /**
7
+ * @var array
8
+ */
9
+ protected $prefixes = array();
10
+
11
+ /**
12
+ * Tribe__Support__Obfuscator constructor.
13
+ *
14
+ * @param array $prefixes
15
+ */
16
+ public function __construct( array $prefixes = array() ) {
17
+ $this->prefixes = $prefixes;
18
+ }
19
+
20
+ /**
21
+ * Whether a value should be obfuscated or not.
22
+ *
23
+ * @param string $key
24
+ *
25
+ * @return bool
26
+ */
27
+ public function should_obfuscate( $key ) {
28
+ foreach ( $this->prefixes as $prefix ) {
29
+ if ( strpos( $key, $prefix ) === 0 ) {
30
+ return true;
31
+ }
32
+ }
33
+
34
+ return false;
35
+ }
36
+
37
+ /**
38
+ * Conditionally obfuscates a string value.
39
+ *
40
+ * @param string $key
41
+ * @param mixed $string_value
42
+ *
43
+ * @return mixed Either the obfuscated string or the original value if not a string.
44
+ */
45
+ public function obfuscate( $key, $string_value ) {
46
+ if ( ! is_string( $string_value ) ) {
47
+ return $string_value;
48
+ }
49
+ if ( ! $this->should_obfuscate( $key ) ) {
50
+ return $string_value;
51
+ }
52
+
53
+ $length = strlen( $string_value );
54
+ if ( $length <= 3 ) {
55
+ return preg_replace( "/./", "#", $string_value );
56
+ } elseif ( $length > 3 && $length <= 5 ) {
57
+ return preg_replace( '/^(.{1}).*$/', '$1' . str_repeat( '#', $length - 1 ) . '$2', $string_value );
58
+ } elseif ( $length > 5 && $length <= 9 ) {
59
+ return preg_replace( '/^(.{1}).*(.{1})$/', '$1' . str_repeat( '#', $length - 2 ) . '$2', $string_value );
60
+ } elseif ( $length > 9 && $length <= 19 ) {
61
+ return preg_replace( '/^(.{2}).*(.{2})$/', '$1' . str_repeat( '#', $length - 4 ) . '$2', $string_value );
62
+ } elseif ( $length > 19 && $length <= 31 ) {
63
+ return preg_replace( '/^(.{3}).*(.{3})$/', '$1' . str_repeat( '#', $length - 6 ) . '$2', $string_value );
64
+ }
65
+
66
+ return preg_replace( '/^(.{4}).*(.{4})$/', '$1' . str_repeat( '#', $length - 8 ) . '$2', $string_value );
67
+ }
68
+ }
common/src/Tribe/Support/Template_Checker.php ADDED
@@ -0,0 +1,276 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Examines a plugin's views directory and builds a list of view filenames
4
+ * and their respective version numbers.
5
+ */
6
+ class Tribe__Support__Template_Checker {
7
+ protected $plugin_name = '';
8
+ protected $plugin_version = '';
9
+ protected $plugin_views_dir = '';
10
+ protected $theme_views_dir = '';
11
+
12
+ protected $originals = array();
13
+ protected $overrides = array();
14
+
15
+
16
+ /**
17
+ * Examine the plugin views (and optionally any theme overrides) and analyse
18
+ * the version numbers where possible.
19
+ *
20
+ * @param string $plugin_version
21
+ * @param string $plugin_views_dir
22
+ * @param string $theme_views_dir
23
+ */
24
+ public function __construct( $plugin_version, $plugin_views_dir, $theme_views_dir = '' ) {
25
+ $this->plugin_version = $this->base_version_number( $plugin_version );
26
+ $this->plugin_views_dir = $plugin_views_dir;
27
+ $this->theme_views_dir = $theme_views_dir;
28
+
29
+ $this->scan_view_directory();
30
+ $this->scan_for_overrides();
31
+ }
32
+
33
+ /**
34
+ * Given a version number with an alpha/beta type suffix, strips that suffix and
35
+ * returns the "base" version number.
36
+ *
37
+ * For example, given "9.8.2beta1" this method will return "9.8.2".
38
+ *
39
+ * The utility of this is that if the author of a template change sets the
40
+ * version tag in the template header to 9.8.2 (to continue the same example) we
41
+ * don't need to worry about updating that for each alpha, beta or RC we put out.
42
+ *
43
+ * @param string $version_number
44
+ *
45
+ * @return string
46
+ */
47
+ protected function base_version_number( $version_number ) {
48
+ return preg_replace( '/[a-z]+[a-z0-9]*$/i', '', $version_number );
49
+ }
50
+
51
+ /**
52
+ * Recursively scans the plugin's view directory and examines the template headers
53
+ * of each file it finds within.
54
+ */
55
+ protected function scan_view_directory() {
56
+ // If the provided directory is invalid flag the problem and go no further
57
+ if ( $this->bad_directory( $this->plugin_views_dir ) ) {
58
+ return;
59
+ }
60
+
61
+ $view_directory = new RecursiveDirectoryIterator( $this->plugin_views_dir );
62
+ $directory_list = new RecursiveIteratorIterator( $view_directory );
63
+
64
+ foreach ( $directory_list as $file ) {
65
+ $this->scan_view( $file );
66
+ }
67
+ }
68
+
69
+ /**
70
+ * Scans an individual view file, adding it's version number (if found) to the
71
+ * $this->views array.
72
+ *
73
+ * @param SplFileInfo $file
74
+ */
75
+ protected function scan_view( SplFileInfo $file ) {
76
+ if ( ! $file->isFile() || ! $file->isReadable() ) {
77
+ return;
78
+ }
79
+
80
+ $version = $this->get_template_version( $file->getPathname() );
81
+ $this->originals[ $this->short_name( $file->getPathname() ) ] = $version;
82
+ }
83
+
84
+ protected function scan_for_overrides() {
85
+ // If the provided directory is invalid flag the problem and go no further
86
+ if ( $this->bad_directory( $this->theme_views_dir ) ) {
87
+ return;
88
+ }
89
+
90
+ foreach ( $this->originals as $view_file => $current_version ) {
91
+ $override_path = trailingslashit( $this->theme_views_dir ) . $view_file;
92
+
93
+ if ( ! is_file( $override_path ) || ! is_readable( $override_path ) ) {
94
+ continue;
95
+ }
96
+
97
+ $this->overrides[ $view_file ] = $this->get_template_version( $override_path );
98
+ }
99
+ }
100
+
101
+ /**
102
+ * Tests to ensure the provided view directory path is invalid or unreadable.
103
+ *
104
+ * @param string $directory
105
+ * @return bool
106
+ */
107
+ protected function bad_directory( $directory ) {
108
+ if ( is_dir( $directory ) && is_readable( $directory ) ) {
109
+ return false;
110
+ }
111
+
112
+ return true;
113
+ }
114
+
115
+ /**
116
+ * Inspects the template header block within the specified file and extracts the
117
+ * version number, if one can be found.
118
+ *
119
+ * @param string $template_filepath
120
+ * @return string
121
+ */
122
+ protected function get_template_version( $template_filepath ) {
123
+ if ( ! is_file( $template_filepath ) || ! is_readable( $template_filepath ) ) {
124
+ return '';
125
+ }
126
+
127
+ $view_content = file_get_contents( $template_filepath );
128
+
129
+ if ( ! preg_match( '/^\s*\*\s*@version\s*([0-9\.]+)/mi', $view_content, $matches ) ) {
130
+ return '';
131
+ }
132
+
133
+ return $matches[1];
134
+ }
135
+
136
+ /**
137
+ * Given a full filepath (ie, to a view file), chops off the base path found
138
+ * in $this->plugin_views_dir.
139
+ *
140
+ * For example, given:
141
+ *
142
+ * $this->plugin_views_dir = '/srv/project/wp-content/plugins/my-plugin/views'
143
+ * $full_filepath = '/srv/project/wp-content/plugins/my-plugin/views/modules/icon.php'
144
+ *
145
+ * Returns:
146
+ *
147
+ * 'modules/icon.php'
148
+ *
149
+ * @param string $full_filepath
150
+ * @return string
151
+ */
152
+ protected function short_name( $full_filepath ) {
153
+ if ( 0 === strpos( $full_filepath, $this->plugin_views_dir ) ) {
154
+ return trim( substr( $full_filepath, strlen( $this->plugin_views_dir ) ), DIRECTORY_SEPARATOR );
155
+ }
156
+
157
+ return $full_filepath;
158
+ }
159
+
160
+ /**
161
+ * Returns an array of the plugin's shipped view files, where each key is the
162
+ * view filename and the value is the version it was last updated.
163
+ *
164
+ * @return array
165
+ */
166
+ public function get_views() {
167
+ return $this->originals;
168
+ }
169
+
170
+ /**
171
+ * Returns an array of any or all of the plugin's shipped view files that contain
172
+ * a version field in their header blocks.
173
+ *
174
+ * @see $this->get_views() for format of returned array
175
+ *
176
+ * @return array
177
+ */
178
+ public function get_versioned_views() {
179
+ $versioned_views = array();
180
+
181
+ foreach ( $this->originals as $key => $version ) {
182
+ if ( ! empty( $version ) ) {
183
+ $versioned_views[ $key ] = $version;
184
+ }
185
+ }
186
+
187
+ return $versioned_views;
188
+ }
189
+
190
+ /**
191
+ * Returns an array of any shipped plugin views that were updated or introduced
192
+ * with the current release (as specified by $this->plugin_version).
193
+ *
194
+ * @see $this->get_views() for format of returned array
195
+ *
196
+ * @return array
197
+ */
198
+ public function get_views_tagged_this_release() {
199
+ $currently_tagged_views = array();
200
+
201
+ foreach ( $this->get_versioned_views() as $key => $version ) {
202
+ if ( $version === $this->plugin_version ) {
203
+ $currently_tagged_views[ $key ] = $version;
204
+ }
205
+ }
206
+
207
+ return $currently_tagged_views;
208
+ }
209
+
210
+ /**
211
+ * Returns an array of theme overrides, where each key is the view filename and the
212
+ * value is the version it was last updated (may be empty).
213
+ *
214
+ * @return array
215
+ */
216
+ public function get_overrides() {
217
+ return $this->overrides;
218
+ }
219
+
220
+ /**
221
+ * Returns an array of any or all theme overrides that contain a version field in their
222
+ * header blocks.
223
+ *
224
+ * @see $this->get_overrides() for format of returned array
225
+ *
226
+ * @return array
227
+ */
228
+ public function get_versioned_overrides() {
229
+ $versioned_views = array();
230
+
231
+ foreach ( $this->overrides as $key => $version ) {
232
+ if ( ! empty( $version ) ) {
233
+ $versioned_views[ $key ] = $version;
234
+ }
235
+ }
236
+
237
+ return $versioned_views;
238
+ }
239
+
240
+ /**
241
+ * Returns an array of any or all theme overrides that seem to be based on an earlier
242
+ * version than that which currently ships with the plugin.
243
+ *
244
+ * If optional param $include_unknown is set to true, the list will include theme
245
+ * overrides where the version could not be determined (for instance, this might result
246
+ * in theme overrides where the template header - or version tag - was removed being
247
+ * included).
248
+ *
249
+ * @see $this->get_overrides() for format of returned array
250
+ *
251
+ * @param bool $include_unknown = false
252
+ * @return array
253
+ */
254
+ public function get_outdated_overrides( $include_unknown = false ) {
255
+ $outdated = array();
256
+ $originals = $this->get_versioned_views();
257
+
258
+ $overrides = $include_unknown
259
+ ? $this->get_overrides()
260
+ : $this->get_versioned_overrides();
261
+
262
+ foreach ( $overrides as $view => $override_version ) {
263
+ if ( empty( $originals[ $view ] ) ) {
264
+ continue;
265
+ }
266
+
267
+ $shipped_version = $originals[ $view ];
268
+
269
+ if ( version_compare( $shipped_version, $override_version, '>' ) ) {
270
+ $outdated[ $view ] = $override_version;
271
+ }
272
+ }
273
+
274
+ return $outdated;
275
+ }
276
+ }
common/src/Tribe/Support/Template_Checker_Report.php ADDED
@@ -0,0 +1,119 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Assembles a report of recently updated plugin views and template overrides in
4
+ * possible revision, for each plugin that registers itself and its template
5
+ * filepaths.
6
+ */
7
+ class Tribe__Support__Template_Checker_Report {
8
+ const VERSION_INDEX = 0;
9
+ const INCLUDED_VIEWS_INDEX = 1;
10
+ const THEME_OVERRIDES_INDEX = 2;
11
+
12
+ /**
13
+ * Contains the individual view/template reports for each registered plugin.
14
+ *
15
+ * @var array
16
+ */
17
+ protected static $plugin_reports = array();
18
+
19
+ /**
20
+ * Container for finished report.
21
+ *
22
+ * @var string
23
+ */
24
+ protected static $complete_report = '';
25
+
26
+ /**
27
+ * Provides an up-to-date report concerning template changes.
28
+ *
29
+ * @return string
30
+ */
31
+ public static function generate() {
32
+ foreach ( self::registered_plugins() as $plugin_name => $plugin_template_system ) {
33
+ self::generate_for( $plugin_name, $plugin_template_system );
34
+ }
35
+
36
+ self::wrap_report();
37
+ return self::$complete_report;
38
+ }
39
+
40
+ protected static function registered_plugins() {
41
+ /**
42
+ * Provides a mechanism for plugins to register information about their template/view
43
+ * setups.
44
+ *
45
+ * This should be done by adding an entry to $registere_template_systems where the key
46
+ * should be the plugin name and the element an array structured as follows:
47
+ *
48
+ * [
49
+ * plugin_version,
50
+ * path_to_included_views,
51
+ * path_to_theme_overrides
52
+ * ]
53
+ *
54
+ * @var array $registered_template_systems
55
+ */
56
+ return apply_filters( 'tribe_support_registered_template_systems', array() );
57
+ }
58
+
59
+ /**
60
+ * Creates a report for the specified plugin.
61
+ *
62
+ * @param string $plugin_name
63
+ * @param array $template_system
64
+ */
65
+ protected static function generate_for( $plugin_name, array $template_system ) {
66
+ $report = '<dt>' . esc_html( $plugin_name ) . '</dt>';
67
+
68
+ $scanner = new Tribe__Support__Template_Checker(
69
+ $template_system[ self::VERSION_INDEX ],
70
+ $template_system[ self::INCLUDED_VIEWS_INDEX ],
71
+ $template_system[ self::THEME_OVERRIDES_INDEX ]
72
+ );
73
+
74
+ $newly_introduced_or_updated = $scanner->get_views_tagged_this_release();
75
+ $outdated_or_unknown = $scanner->get_outdated_overrides( true );
76
+
77
+ if ( empty( $newly_introduced_or_updated ) && empty( $outdated_or_unknown ) ) {
78
+ $report .= '<dd>' . __( 'No notable changes detected', 'tribe-common' ) . '</dd>';
79
+ }
80
+
81
+ if ( ! empty( $newly_introduced_or_updated ) ) {
82
+ $report .= '<dd><p>' . sprintf( __( 'Templates introduced or updated with this release (%s):', 'tribe-common' ), $template_system[ self::VERSION_INDEX ] ) . '</p><ul>';
83
+
84
+ foreach ( $newly_introduced_or_updated as $view_name => $version ) {
85
+ $report .= '<li>' . esc_html( $view_name ) . '</li>';
86
+ }
87
+
88
+ $report .= '</ul></dd>';
89
+ }
90
+
91
+ if ( ! empty( $outdated_or_unknown ) ) {
92
+ $report .= '<dd><p>' . __( 'Existing theme overrides that may need revision:', 'tribe-common' ) . '</p><ul>';
93
+
94
+ foreach ( $outdated_or_unknown as $view_name => $version ) {
95
+ $version_note = empty( $version )
96
+ ? __( 'version data missing from override', 'tribe-common' )
97
+ : sprintf( __( 'based on %s version', 'tribe-common' ), $version );
98
+
99
+ $report .= '<li>' . esc_html( $view_name ) . ' (' . $version_note . ') </li>';
100
+ }
101
+
102
+ $report .= '</ul></dd>';
103
+ }
104
+
105
+ self::$plugin_reports[ $plugin_name ] = $report;
106
+ }
107
+
108
+ /**
109
+ * Wraps the individual plugin template reports ready for display.
110
+ */
111
+ protected static function wrap_report() {
112
+ if ( empty( self::$plugin_reports ) ) {
113
+ self::$complete_report = '<p>' . __( 'No notable template changes detected.', 'tribe-common' ) . '</p>';
114
+ } else {
115
+ self::$complete_report = '<p>' . __( 'Information about recent template changes and potentially impacted template overrides is provided below.', 'tribe-common' ) . '</p>'
116
+ . '<div class="template-updates-wrapper">' . join( ' ', self::$plugin_reports ) . '</div>';
117
+ }
118
+ }
119
+ }
common/src/Tribe/Template_Factory.php CHANGED
File without changes
common/src/Tribe/Template_Part_Cache.php CHANGED
File without changes
common/src/Tribe/Validate.php CHANGED
File without changes
common/src/Tribe/View_Helpers.php CHANGED
File without changes
common/src/admin-views/tribe-options-display.php CHANGED
File without changes
common/src/admin-views/tribe-options-general.php CHANGED
File without changes
common/src/admin-views/tribe-options-help.php CHANGED
@@ -33,6 +33,9 @@ $help->add_section_content( 'extra-help', '<div style="text-align: right;"><a hr
33
  // Creates the System Info section
34
  $help->add_section( 'system-info', __( 'System Information', 'tribe-common' ), 30 );
35
  $help->add_section_content( 'system-info', __( 'The details of your calendar plugin and settings is often needed for you or our staff to help troubleshoot an issue. We may ask you to share this information if you ask for support. If you post in one of our premium forums, please copy and paste this information into the System Information field and it will help us help you faster!', 'tribe-common' ), 0 );
 
 
 
36
  ?>
37
 
38
  <div id="tribe-help-general">
33
  // Creates the System Info section
34
  $help->add_section( 'system-info', __( 'System Information', 'tribe-common' ), 30 );
35
  $help->add_section_content( 'system-info', __( 'The details of your calendar plugin and settings is often needed for you or our staff to help troubleshoot an issue. We may ask you to share this information if you ask for support. If you post in one of our premium forums, please copy and paste this information into the System Information field and it will help us help you faster!', 'tribe-common' ), 0 );
36
+
37
+ $help->add_section( 'template-changes', __( 'Recent Template Changes', 'tribe-common' ), 40 );
38
+ $help->add_section_content( 'template-changes', Tribe__Support__Template_Checker_Report::generate() );
39
  ?>
40
 
41
  <div id="tribe-help-general">
common/src/admin-views/tribe-options-licenses.php CHANGED
File without changes
common/src/admin-views/tribe-options-network.php CHANGED
File without changes
common/src/functions/template-tags/date.php CHANGED
File without changes
common/src/functions/template-tags/general.php CHANGED
@@ -158,8 +158,7 @@ if ( ! function_exists( 'tribe_get_date_format' ) ) {
158
  $format = tribe_get_date_option( 'dateWithoutYearFormat', 'F j' );
159
  }
160
 
161
- // Strip slashes - otherwise the slashes for escaped characters will themselves be escaped
162
- return apply_filters( 'tribe_date_format', stripslashes( $format ) );
163
  }
164
  }//end if
165
 
158
  $format = tribe_get_date_option( 'dateWithoutYearFormat', 'F j' );
159
  }
160
 
161
+ return apply_filters( 'tribe_date_format', $format );
 
162
  }
163
  }//end if
164
 
common/src/resources/css/tribe-common-admin.css CHANGED
@@ -210,7 +210,8 @@ table.plugins .tribe-plugin-update-message a {
210
  list-style: circle;
211
  }
212
 
213
- #tribe-system-info dl.support-stats {
 
214
  background: #000;
215
  color: #888;
216
  padding: 10px;
@@ -219,7 +220,8 @@ table.plugins .tribe-plugin-update-message a {
219
  border-radius: 2px;
220
  }
221
 
222
- #tribe-system-info dl.support-stats dt {
 
223
  text-transform: uppercase;
224
  font-weight: bold;
225
  width: 25%;
@@ -227,13 +229,15 @@ table.plugins .tribe-plugin-update-message a {
227
  float: left;
228
  }
229
 
230
- #tribe-system-info dl.support-stats dd {
 
231
  padding-left: 10px;
232
  margin-left: 25%;
233
  }
234
 
235
-
236
-
 
237
 
238
  #tribe-help-sidebar {
239
  width: 32%;
@@ -301,9 +305,6 @@ table.plugins .tribe-plugin-update-message a {
301
  list-style: disc inside;
302
  }
303
 
304
-
305
-
306
-
307
  /* = jQuery UI
308
  =============================================*/
309
  .ui-widget-overlay {background: #666666; opacity: .50;filter:Alpha(Opacity=50); }
210
  list-style: circle;
211
  }
212
 
213
+ #tribe-system-info dl.support-stats,
214
+ .template-updates-wrapper {
215
  background: #000;
216
  color: #888;
217
  padding: 10px;
220
  border-radius: 2px;
221
  }
222
 
223
+ #tribe-system-info dl.support-stats dt,
224
+ .template-updates-wrapper dt {
225
  text-transform: uppercase;
226
  font-weight: bold;
227
  width: 25%;
229
  float: left;
230
  }
231
 
232
+ #tribe-system-info dl.support-stats dd,
233
+ .template-updates-wrapper dd {
234
  padding-left: 10px;
235
  margin-left: 25%;
236
  }
237
 
238
+ .template-updates-wrapper p {
239
+ margin-top: 0;
240
+ }
241
 
242
  #tribe-help-sidebar {
243
  width: 32%;
305
  list-style: disc inside;
306
  }
307
 
 
 
 
308
  /* = jQuery UI
309
  =============================================*/
310
  .ui-widget-overlay {background: #666666; opacity: .50;filter:Alpha(Opacity=50); }
common/src/resources/js/notice-dismiss.js ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Notice Dismiss structure
3
+ */
4
+ ( function( $ ) {
5
+ // Add / Update a key-value pair in the URL query parameters
6
+ function update_query_string(uri, key, value) {
7
+ // remove the hash part before operating on the uri
8
+ var i = uri.indexOf( '#' );
9
+ var hash = i === -1 ? '' : uri.substr(i);
10
+ uri = i === -1 ? uri : uri.substr(0, i);
11
+
12
+ var re = new RegExp("([?&])" + key + "=.*?(&|$)", "i");
13
+ var separator = uri.indexOf('?') !== -1 ? "&" : "?";
14
+ if (uri.match(re)) {
15
+ uri = uri.replace(re, '$1' + key + "=" + value + '$2');
16
+ } else {
17
+ uri = uri + separator + key + "=" + value;
18
+ }
19
+ return uri + hash; // finally append the hash as well
20
+ }
21
+
22
+ $( document ).ready( function() {
23
+ $( '.tribe-dismiss-notice.is-dismissible' ).on( 'click', '.notice-dismiss', function() {
24
+ window.location.href = update_query_string( window.location.href, 'tribe-dismiss-notice', $( this ).parents( '.tribe-dismiss-notice' ).data( 'ref' ) );
25
+ } );
26
+ } );
27
+ }( jQuery ) );
common/tests.md DELETED
@@ -1,47 +0,0 @@
1
- # Tribe Common tests
2
-
3
- This is a brief and quick guide that's covering the bare essentials needed to set up the tests on your local plugin copy.
4
- Please refer to [Codeception](http://codeception.com/docs) and [WP Browser](https://github.com/lucatume/wp-browser) documentation for any issue that's not TEC related.
5
-
6
- ## Set up
7
- After cloning the TEX repository on your local machine change directory to the plugin root folder and pull in any needed dependency using [Composer](https://getcomposer.org/):
8
-
9
- composer update
10
-
11
- when Composer finished the update process (might take a while) set up your own [Codeception](http://codeception.com/) installation running
12
-
13
- vendor/bin/wpcept bootstrap
14
-
15
- The `wpcept bootstrap` command is a modified version of the default `codecept bootstrap` command that will take care of setting up a WordPress-friendly testing environment.
16
- To be able to run successfully on your system Codeception will need to be configured to look for the right database, the right WordPress installation and so on.
17
- Codeception allows for "distribution" versions of its configuration to be shared among developers, what you define in your local Codeception configuration files will override the "distribution" setting; think of CSS rules.
18
- The repository contains a `codeception.dist.yml` file that Codeception will read before reading the local to your machine `codeception.yml` file.
19
- Copy the distribution version of the Codeception configuration file in the root folder of the plugin
20
-
21
- cp codeception.dist.yml codeception.yml
22
-
23
- **Edit the file `codeception.yml` file to suit your database, installation folder and web driver settings.**
24
-
25
- **Beware**: The `WPLoader` module that's used in functional tests will **destroy** the database it's working on: **do not** point it to the same database you use for development! A good rule of thumb is to have a database for development (e.g. `tec`) and one that will be used for tests (e.g. `tec-tests`).
26
- On the same lines the repository packs "distribution" versions of the `unit.suite.dist.yml`, `functional.suite.dist.yml` and `acceptance.suite.dist.yml` configuration files: there is usually no need to override those but it's worth mentioning they exist.
27
- The last piece of the configuration is the bootstrap file; the repository comes with "distribution" versions of these file in the root folder of the pluging tests (`/tests/_bootstrap.dist.php`) and a bootstrap file specific to each suite (`/tests/acceptance/_bootstrap.dist.php`, `/tests/functional/_bootstrap.dist.php`, `/tests/unit/_bootstrap.dist.php`); remove the root `_bootstrap.php` file Codeception created during bootstrapping and copy the one in the root of the plugin tests (`/tests`)
28
-
29
- rm _bootstrap.php
30
- cp _bootstrap.dist.php _bootstrap.php
31
-
32
- You *should* not need to edit anything in any bootstrap file to make things work. Do the same for the suite specific bootstrap files
33
-
34
- cp acceptance/_bootstrap.dist.php acceptance/_bootstrap.php
35
- cp functional/_bootstrap.dist.php functional/_bootstrap.php
36
- cp unit/_bootstrap.dist.php unit/_bootstrap.php
37
-
38
- ## Running the tests
39
- Nothing different from a default Codeception environment so this command will run all the tests
40
-
41
- vendor/bin/codecept run
42
-
43
- Failing tests are ok in set up terms: the system works. Errors should be reported.
44
- Please refer to [Codeception documentation](http://codeception.com/docs) to learn about more run and configuaration options.
45
-
46
- ## Contributing to tests
47
- Should you come up with good utility methods, worthy database configurations and "cool additions" in general for the plugin tests feel free to open a PR and submit them for review.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
common/tests/wpunit/Tribe/Events/common/Date_UtilsTest.php DELETED
@@ -1,228 +0,0 @@
1
- <?php
2
- namespace Tribe\Events\Common;
3
-
4
- use \Tribe__Date_Utils as Date_Utils;
5
-
6
- class Date_UtilsTest extends \Codeception\TestCase\WPTestCase {
7
-
8
- /**
9
- * @var string
10
- */
11
- protected static $tz_backup;
12
-
13
- protected $backupGlobals = false;
14
-
15
- public static function setUpBeforeClass() {
16
- self::$tz_backup = date_default_timezone_get();
17
-
18
- return parent::setUpBeforeClass();
19
- }
20
-
21
- public static function tearDownAfterClass() {
22
- date_default_timezone_set( self::$tz_backup );
23
-
24
- return parent::tearDownAfterClass();
25
- }
26
-
27
- public function setUp() {
28
- // before
29
- parent::setUp();
30
-
31
- // your set up methods here
32
- }
33
-
34
- public function tearDown() {
35
- // your tear down methods here
36
-
37
- // then
38
- parent::tearDown();
39
- }
40
-
41
- public function bad_argument_formats() {
42
- return array_map( function ( $arr ) {
43
- return [ $arr ];
44
- },
45
- [
46
- [ 'day', 2, 3, 2012, 1 ],
47
- [ 2, 'week', 3, 2012, 1 ],
48
- [ 2, 2, 'month', 2012, 1 ],
49
- [ 2, 2, 3, 'year', 1 ],
50
- [ 2, 2, 3, 2012, 'direction' ],
51
- [ 2, 2, 3, 2012, 23 ],
52
- [ 2, 2, 3, 2012, - 2 ],
53
- ] );
54
- }
55
-
56
- /**
57
- * get_weekday_timestamp returns false for wrong argument format
58
- *
59
- * @dataProvider bad_argument_formats
60
- */
61
- public function test_get_weekday_timestamp_returns_false_if_day_of_week_is_not_int( $args ) {
62
- $this->assertFalse( call_user_func_array( [ 'Tribe__Date_Utils', 'get_weekday_timestamp' ], $args ) );
63
- }
64
-
65
- public function etc_natural_direction_expected_timestamps() {
66
- return [
67
- [ 1420416000, [ 1, 1, 1, 2015, 1 ] ], // Mon, first week of Jan 2015
68
- [ 1423094400, [ 4, 1, 2, 2015, 1 ] ], // Thursday, first week of Feb 2015
69
- [ 1425081600, [ 6, 4, 2, 2015, 1 ] ], // Saturday, 4th week of Feb 2015
70
- ];
71
- }
72
-
73
- /**
74
- * get_weekday_timestamp returns right timestamp etc in natural direction
75
- *
76
- * @dataProvider etc_natural_direction_expected_timestamps
77
- */
78
- public function test_get_weekday_timestamp_returns_right_timestamp_in_etc_natural_direction( $expected, $args ) {
79
- date_default_timezone_set( 'Etc/GMT+0' );
80
- $this->assertEquals( $expected,
81
- call_user_func_array( [
82
- 'Tribe__Date_Utils',
83
- 'get_weekday_timestamp'
84
- ],
85
- $args ) );
86
- }
87
-
88
- /**
89
- * get_weekday_timestamp returns right timestamp etc -9 in natural direction
90
- *
91
- * @dataProvider etc_natural_direction_expected_timestamps
92
- */
93
- public function test_get_weekday_timestamp_returns_right_timestamp_etc_minus_9_in_natural_direction( $expected, $args ) {
94
- date_default_timezone_set( 'Etc/GMT-9' );
95
- $nine_hours = 60 * 60 * 9;
96
- $this->assertEquals( $expected - $nine_hours,
97
- call_user_func_array( [
98
- 'Tribe__Date_Utils',
99
- 'get_weekday_timestamp'
100
- ],
101
- $args ) );
102
- }
103
-
104
- /**
105
- * get_weekday_timestamp returns right timestamp etc +9 in natural direction
106
- *
107
- * @dataProvider etc_natural_direction_expected_timestamps
108
- */
109
- public function test_get_weekday_timestamp_returns_right_timestamp_etc_plus_9_in_natural_direction( $expected, $args ) {
110
- date_default_timezone_set( 'Etc/GMT+9' );
111
- $nine_hours = 60 * 60 * 9;
112
- $this->assertEquals( $expected + $nine_hours,
113
- call_user_func_array( [
114
- 'Tribe__Date_Utils',
115
- 'get_weekday_timestamp'
116
- ],
117
- $args ) );
118
- }
119
-
120
- public function etc_reverse_direction_expected_timestamps() {
121
- return [
122
- [ 1422230400, [ 1, 1, 1, 2015, - 1 ] ], // Mon, last week of Jan 2015
123
- [ 1424908800, [ 4, 1, 2, 2015, - 1 ] ], // Thursday, last week of Feb 2015
124
- [ 1424476800, [ 6, 2, 2, 2015, - 1 ] ], // Saturday, penultimate week of Feb 2015
125
- [ 1423872000, [ 6, 3, 2, 2015, - 1 ] ], // Saturday, antepenultimate week of Feb 2015
126
- ];
127
- }
128
-
129
- /**
130
- * get_weekday_timestamp returns right timestamp etc in reverse direction
131
- *
132
- * @dataProvider etc_reverse_direction_expected_timestamps
133
- */
134
- public function test_get_weekday_timestamp_returns_right_timestamp_in_etc_reverse_direction( $expected, $args ) {
135
- date_default_timezone_set( 'Etc/GMT+0' );
136
- $this->assertEquals( $expected,
137
- call_user_func_array( [
138
- 'Tribe__Date_Utils',
139
- 'get_weekday_timestamp'
140
- ],
141
- $args ) );
142
- }
143
-
144
- /**
145
- * get_weekday_timestamp returns right timestamp etc -9 in reverse direction
146
- *
147
- * @dataProvider etc_reverse_direction_expected_timestamps
148
- */
149
- public function test_get_weekday_timestamp_returns_right_timestamp_etc_minus_9_in_reverse_direction( $expected, $args ) {
150
- date_default_timezone_set( 'Etc/GMT-9' );
151
- $nine_hours = 60 * 60 * 9;
152
- $this->assertEquals( $expected - $nine_hours,
153
- call_user_func_array( [
154
- 'Tribe__Date_Utils',
155
- 'get_weekday_timestamp'
156
- ],
157
- $args ) );
158
- }
159
-
160
- /**
161
- * get_weekday_timestamp returns right timestamp etc +9 in reverse direction
162
- *
163
- * @dataProvider etc_reverse_direction_expected_timestamps
164
- */
165
- public function test_get_weekday_timestamp_returns_right_timestamp_etc_plus_9_in_reverse_direction( $expected, $args ) {
166
- date_default_timezone_set( 'Etc/GMT+9' );
167
- $nine_hours = 60 * 60 * 9;
168
- $this->assertEquals( $expected + $nine_hours,
169
- call_user_func_array( [
170
- 'Tribe__Date_Utils',
171
- 'get_weekday_timestamp'
172
- ],
173
- $args ) );
174
- }
175
-
176
- /**
177
- * unescape_date_format will return input if not a string
178
- */
179
- public function test_unescape_date_format_will_return_input_if_not_a_string() {
180
- $bad_input = array( 23 );
181
- $this->assertEquals( $bad_input, Date_Utils::unescape_date_format( $bad_input ) );
182
- }
183
-
184
- public function date_formats_not_to_escape() {
185
- return [
186
- [ 'tribe', 'tribe' ],
187
- [ 'j \d\e F', 'j \d\e F' ],
188
- [ 'F, \e\l j' , 'F, \e\l j' ],
189
- [ '\hH', '\hH' ],
190
- [ 'i\m, s\s', 'i\m, s\s' ],
191
- [ '\T\Z: T ', '\T\Z: T' ],
192
- ];
193
- }
194
-
195
- /**
196
- * unescape_date_format will return same string when nothing to escape
197
- *
198
- * @dataProvider date_formats_not_to_escape
199
- */
200
- public function test_unescape_date_format_will_return_same_string_when_nothing_to_escape( $in ) {
201
- $out = Date_Utils::unescape_date_format( $in );
202
- $this->assertEquals( $in, $out );
203
- }
204
-
205
- public function date_formats_to_escape() {
206
- return [
207
- [ 'j \\d\\e F', 'j \d\e F' ],
208
- [ 'F, \\e\\l j' , 'F, \e\l j' ],
209
- [ '\\hH', '\hH' ],
210
- [ 'i\\m, s\\s', 'i\m, s\s' ],
211
- [ '\\T\\Z: T', '\T\Z: T' ],
212
- [ 'j \d\\e F', 'j \d\e F' ],
213
- [ 'F, \e\\l j' , 'F, \e\l j' ],
214
- [ 'i\m, s\\s', 'i\m, s\s' ],
215
- [ '\T\\Z: T' , '\T\Z: T' ],
216
- ];
217
- }
218
-
219
- /**
220
- * unescape_date_format will return escaped date format
221
- *
222
- * @dataProvider date_formats_to_escape
223
- */
224
- public function test_unescape_date_format_will_return_escaped_date_format( $in, $expected_out ) {
225
- $out = Date_Utils::unescape_date_format( $in );
226
- $this->assertEquals( $expected_out, $out );
227
- }
228
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
common/tribe-autoload.php CHANGED
@@ -1,7 +1,8 @@
1
  <?php
2
- $src = dirname( __FILE__ ) . '/src';
3
- require_once $src . '/Tribe/Autoloader.php';
 
4
 
5
  $autoloader = Tribe__Autoloader::instance();
6
- $autoloader->register_prefix('Tribe__',$src);
7
  $autoloader->register_autoloader();
1
  <?php
2
+ $common = dirname( __FILE__ ) . '/src';
3
+
4
+ require_once $common . '/Tribe/Autoloader.php';
5
 
6
  $autoloader = Tribe__Autoloader::instance();
7
+ $autoloader->register_prefix( 'Tribe__', $common . '/Tribe' );
8
  $autoloader->register_autoloader();
common/tribe-common.php CHANGED
@@ -1,7 +1,7 @@
1
  <?php
2
  /*
3
  Description: An event settings framework for managing shared options
4
- Version: 4.0.6
5
  Author: Modern Tribe, Inc.
6
  Author URI: http://m.tri.be/1x
7
  Text Domain: tribe-common
1
  <?php
2
  /*
3
  Description: An event settings framework for managing shared options
4
+ Version: 4.1
5
  Author: Modern Tribe, Inc.
6
  Author URI: http://m.tri.be/1x
7
  Text Domain: tribe-common
common/vendor/jquery/images/ui-bg_flat_0_aaaaaa_40x100.png CHANGED
File without changes
common/vendor/jquery/images/ui-bg_flat_75_ffffff_40x100.png CHANGED
File without changes
common/vendor/jquery/images/ui-bg_glass_55_fbf9ee_1x400.png CHANGED
File without changes
common/vendor/jquery/images/ui-bg_glass_65_ffffff_1x400.png CHANGED
File without changes
common/vendor/jquery/images/ui-bg_glass_75_dadada_1x400.png CHANGED
File without changes
common/vendor/jquery/images/ui-bg_glass_75_e6e6e6_1x400.png CHANGED
File without changes
common/vendor/jquery/images/ui-bg_glass_95_fef1ec_1x400.png CHANGED
File without changes
common/vendor/jquery/images/ui-bg_highlight-soft_75_cccccc_1x100.png CHANGED
File without changes
common/vendor/jquery/images/ui-icons_222222_256x240.png CHANGED
File without changes
common/vendor/jquery/images/ui-icons_2e83ff_256x240.png CHANGED
File without changes
common/vendor/jquery/images/ui-icons_454545_256x240.png CHANGED
File without changes
common/vendor/jquery/images/ui-icons_888888_256x240.png CHANGED
File without changes
common/vendor/jquery/images/ui-icons_cd0a0a_256x240.png CHANGED
File without changes
common/vendor/jquery/ui.theme.css CHANGED
File without changes
event-tickets.php CHANGED
@@ -2,7 +2,7 @@
2
  /*
3
  Plugin Name: Event Tickets
4
  Description: Event Tickets allows you to sell tickets to events
5
- Version: 4.0.6
6
  Author: Modern Tribe, Inc.
7
  Author URI: http://m.tri.be/28
8
  License: GPLv2 or later
2
  /*
3
  Plugin Name: Event Tickets
4
  Description: Event Tickets allows you to sell tickets to events
5
+ Version: 4.1
6
  Author: Modern Tribe, Inc.
7
  Author URI: http://m.tri.be/28
8
  License: GPLv2 or later
lang/event-tickets-es_ES.mo CHANGED
Binary file
lang/event-tickets-es_ES.po CHANGED
@@ -2,7 +2,7 @@
2
  # This file is distributed under the same license as the Event Tickets package.
3
  msgid ""
4
  msgstr ""
5
- "PO-Revision-Date: 2015-12-02 18:12:07+0000\n"
6
  "MIME-Version: 1.0\n"
7
  "Content-Type: text/plain; charset=UTF-8\n"
8
  "Content-Transfer-Encoding: 8bit\n"
@@ -760,7 +760,7 @@ msgstr "Maldivas"
760
 
761
  #: src/Tribe/RSVP.php:349
762
  msgid "Your tickets from %s"
763
- msgstr ""
764
 
765
  #: common/src/Tribe/View_Helpers.php:179
766
  msgid "Mali"
@@ -1193,7 +1193,7 @@ msgstr "Senegal"
1193
 
1194
  #: src/admin-views/rsvp-metabox-advanced.php:6
1195
  msgid "(Total available # of this ticket type. Once they're gone, ticket type is sold out.)"
1196
- msgstr ""
1197
 
1198
  #: common/src/Tribe/View_Helpers.php:235
1199
  msgid "Serbia"
@@ -1201,7 +1201,7 @@ msgstr "Serbia"
1201
 
1202
  #: src/admin-views/rsvp-metabox-advanced.php:16
1203
  msgid "Selling tickets for recurring events"
1204
- msgstr ""
1205
 
1206
  #: common/src/Tribe/View_Helpers.php:236
1207
  msgid "Seychelles"
2
  # This file is distributed under the same license as the Event Tickets package.
3
  msgid ""
4
  msgstr ""
5
+ "PO-Revision-Date: 2016-03-11 22:36:31+0000\n"
6
  "MIME-Version: 1.0\n"
7
  "Content-Type: text/plain; charset=UTF-8\n"
8
  "Content-Transfer-Encoding: 8bit\n"
760
 
761
  #: src/Tribe/RSVP.php:349
762
  msgid "Your tickets from %s"
763
+ msgstr "Tus tickets de parte de %s"
764
 
765
  #: common/src/Tribe/View_Helpers.php:179
766
  msgid "Mali"
1193
 
1194
  #: src/admin-views/rsvp-metabox-advanced.php:6
1195
  msgid "(Total available # of this ticket type. Once they're gone, ticket type is sold out.)"
1196
+ msgstr "(# Total disponible para este categoría de boleto. Una vez vendidos, esta categoría está agotada)"
1197
 
1198
  #: common/src/Tribe/View_Helpers.php:235
1199
  msgid "Serbia"
1201
 
1202
  #: src/admin-views/rsvp-metabox-advanced.php:16
1203
  msgid "Selling tickets for recurring events"
1204
+ msgstr "Vender tickets para eventos recurrentes"
1205
 
1206
  #: common/src/Tribe/View_Helpers.php:236
1207
  msgid "Seychelles"
readme.txt CHANGED
@@ -3,8 +3,8 @@
3
  Contributors: ModernTribe, borkweb, zbtirrell, barry.hughes, bordoni, brianjessee, brook-tribe, faction23, geoffgraham, ggwicz, jazbek, jbrinley, joshlimecuda, leahkoerper, lucatume, mastromktg, neillmcshea, nicosantos, peterchester, reid.peifer, roblagatta, shane.pearlman, thatdudebutch
4
  Tags: events, add-on, ticket sales, tickets, calendar, community, registration, api, dates, date, posts, workshop, conference, meeting, seminar, concert, summit, The Events Calendar, Events Calendar PRO, ticket integration, event ticketing, RSVP, Event Tickets, Event Tickets Plus
5
  Requires at least: 3.9
6
- Tested up to: 4.4
7
- Stable tag: 4.0.6
8
  License: GPLv2 or later
9
  License URI: http://www.gnu.org/licenses/gpl-2.0.html
10
 
@@ -179,8 +179,15 @@ Our Premium Plugins:
179
 
180
  == Changelog ==
181
 
182
- = [4.0.6] 2016-03-02 =
183
 
 
 
 
 
 
 
 
184
  * Fix - Prevent notices to enqueue method when moving form hooks
185
 
186
  = [4.0.5] 2016-02-17 =
@@ -189,6 +196,9 @@ Our Premium Plugins:
189
 
190
  = [4.0.4] 2015-12-23 =
191
 
 
 
 
192
  * Fix - Resolved issue with stock calculations on the Attendees report
193
 
194
  = [4.0.3] 2015-12-22 =
3
  Contributors: ModernTribe, borkweb, zbtirrell, barry.hughes, bordoni, brianjessee, brook-tribe, faction23, geoffgraham, ggwicz, jazbek, jbrinley, joshlimecuda, leahkoerper, lucatume, mastromktg, neillmcshea, nicosantos, peterchester, reid.peifer, roblagatta, shane.pearlman, thatdudebutch
4
  Tags: events, add-on, ticket sales, tickets, calendar, community, registration, api, dates, date, posts, workshop, conference, meeting, seminar, concert, summit, The Events Calendar, Events Calendar PRO, ticket integration, event ticketing, RSVP, Event Tickets, Event Tickets Plus
5
  Requires at least: 3.9
6
+ Tested up to: 4.4.2
7
+ Stable tag: 4.1
8
  License: GPLv2 or later
9
  License URI: http://www.gnu.org/licenses/gpl-2.0.html
10
 
179
 
180
  == Changelog ==
181
 
182
+ = [4.1] 2016-03-15 =
183
 
184
+ * Feature - Implemented global stock per event allowing multiple tickets to pull from the same pool of available tickets on an event (Heck yeah to all those who voted on this feature!)
185
+ * Feature - Added filters for RSVP ticket generation: event_tickets_rsvp_tickets_created, event_tickets_rsvp_tickets_generated_for_product, and event_tickets_rsvp_tickets_generated (props to 75ninteen for this pull request!)
186
+ * Tweak - Conditionally show attendees link on Event listing in the WordPress administration
187
+ * Tweak - Obfuscated license keys Events > Help > System Information
188
+ * Tweak - Allowed the "same slug" notice to be dismissed and fix some text in that message
189
+ * Fix - Fixed issue where some characters were not escaped appropriately for month and year formats
190
+ * Fix - Resolved issue where the RSVP confirmation error message displayed when it shouldn't
191
  * Fix - Prevent notices to enqueue method when moving form hooks
192
 
193
  = [4.0.5] 2016-02-17 =
196
 
197
  = [4.0.4] 2015-12-23 =
198
 
199
+ * Feature - Add support for global ticket stock so multiple tickets can optionally reduce from a single ticket total for a given event
200
+ * Tweak - Ignore alpha/beta/rc suffixes on version numbers when checking template versions
201
+ * Tweak - Add HTML id attribute to ticket area on the single-event page so plugin/theme authors can use anchor tags to jump to that section of the page
202
  * Fix - Resolved issue with stock calculations on the Attendees report
203
 
204
  = [4.0.3] 2015-12-22 =
src/Tribe/Attendees_Table.php CHANGED
@@ -75,8 +75,6 @@ class Tribe__Tickets__Attendees_Table extends WP_List_Table {
75
  'cb' => '<input type="checkbox" />',
76
  'order_id' => esc_html__( 'Order #', 'event-tickets' ),
77
  'order_status' => esc_html__( 'Order Status', 'event-tickets' ),
78
- 'purchaser_name' => esc_html__( 'Purchaser name', 'event-tickets' ),
79
- 'purchaser_email' => esc_html__( 'Purchaser email', 'event-tickets' ),
80
  'ticket' => esc_html__( 'Ticket type', 'event-tickets' ),
81
  'attendee_id' => esc_html__( 'Ticket #', 'event-tickets' ),
82
  'security' => esc_html__( 'Security Code', 'event-tickets' ),
@@ -86,7 +84,6 @@ class Tribe__Tickets__Attendees_Table extends WP_List_Table {
86
  return $columns;
87
  }
88
 
89
-
90
  /**
91
  * Handler for the columns that don't have a specific column_{name} handler function.
92
  *
@@ -147,12 +144,9 @@ class Tribe__Tickets__Attendees_Table extends WP_List_Table {
147
 
148
  // If the warning flag is set, add the appropriate icon
149
  if ( $warning ) {
150
- $resources_url = plugins_url( 'resources', dirname( dirname( __FILE__ ) ) );
151
-
152
- $icon = sprintf( "<span class='warning'><img src='%s'/></span> ", $resources_url . '/images/warning.png' );
153
  }
154
 
155
-
156
  // Look for an order_status_label, fall back on the actual order_status string @todo remove fallback in 3.4.3
157
  if ( empty( $item['order_status'] ) ) {
158
  $item['order_status'] = '';
@@ -162,6 +156,41 @@ class Tribe__Tickets__Attendees_Table extends WP_List_Table {
162
  return $icon . $label;
163
  }
164
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
165
  /**
166
  * Handler for the check in column
167
  *
@@ -170,6 +199,42 @@ class Tribe__Tickets__Attendees_Table extends WP_List_Table {
170
  * @return string
171
  */
172
  public function column_check_in( $item ) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
173
  $checkin = sprintf( '<a href="#" data-attendee-id="%d" data-provider="%s" class="button-secondary tickets_checkin">%s</a>', esc_attr( $item['attendee_id'] ), esc_attr( $item['provider'] ), esc_html__( 'Check in', 'event-tickets' ) );
174
  $uncheckin = sprintf( '<span class="delete"><a href="#" data-attendee-id="%d" data-provider="%s" class="tickets_uncheckin">%s</a></span>', esc_attr( $item['attendee_id'] ), esc_attr( $item['provider'] ), esc_html__( 'Undo Check in', 'event-tickets' ) );
175
 
@@ -193,8 +258,14 @@ class Tribe__Tickets__Attendees_Table extends WP_List_Table {
193
  echo '<tr class="' . sanitize_html_class( $row_class ) . esc_attr( $checked ) . '">';
194
  $this->single_row_columns( $item );
195
  echo '</tr>';
196
- }
197
 
 
 
 
 
 
 
 
198
 
199
  /**
200
  * Extra controls to be displayed between bulk actions and pagination.
@@ -259,7 +330,6 @@ class Tribe__Tickets__Attendees_Table extends WP_List_Table {
259
  return (array) apply_filters( 'tribe_events_tickets_attendees_table_bulk_actions', $actions );
260
  }
261
 
262
-
263
  /**
264
  * Handler for the different bulk actions
265
  */
75
  'cb' => '<input type="checkbox" />',
76
  'order_id' => esc_html__( 'Order #', 'event-tickets' ),
77
  'order_status' => esc_html__( 'Order Status', 'event-tickets' ),
 
 
78
  'ticket' => esc_html__( 'Ticket type', 'event-tickets' ),
79
  'attendee_id' => esc_html__( 'Ticket #', 'event-tickets' ),
80
  'security' => esc_html__( 'Security Code', 'event-tickets' ),
84
  return $columns;
85
  }
86
 
 
87
  /**
88
  * Handler for the columns that don't have a specific column_{name} handler function.
89
  *
144
 
145
  // If the warning flag is set, add the appropriate icon
146
  if ( $warning ) {
147
+ $icon = sprintf( "<span class='warning'><img src='%s'/></span> ", esc_url( Tribe__Tickets__Main::instance()->plugin_url . 'src/resources/images/warning.png' ) );
 
 
148
  }
149
 
 
150
  // Look for an order_status_label, fall back on the actual order_status string @todo remove fallback in 3.4.3
151
  if ( empty( $item['order_status'] ) ) {
152
  $item['order_status'] = '';
156
  return $icon . $label;
157
  }
158
 
159
+ /**
160
+ * Handler for the ticket column
161
+ *
162
+ * @since 4.1
163
+ *
164
+ * @param array $item Item whose ticket data should be output
165
+ *
166
+ * @return string
167
+ */
168
+ public function column_ticket( $item ) {
169
+ ob_start();
170
+
171
+ $acquired_by_label = 'Tribe__Tickets__RSVP' === $item['provider'] ? __( 'Reserved by:', 'event-tickets' ) : __( 'Purchased by:', 'event-tickets' );
172
+
173
+ ?>
174
+ <div class="event-tickets-ticket-name">
175
+ <?php echo esc_html( $item['ticket'] ); ?>
176
+ </div>
177
+ <div class="event-tickets-ticket-purchaser">
178
+ <?php echo esc_html( $acquired_by_label ); ?> <?php echo esc_html( $item['purchaser_name'] ); ?> (<?php echo esc_html( $item['purchaser_email'] ); ?>)
179
+ </div>
180
+ <?php
181
+
182
+ /**
183
+ * Hook to allow for the insertion of additional content in the ticket table cell
184
+ *
185
+ * @var $item Attendee row item
186
+ */
187
+ do_action( 'event_tickets_attendees_table_ticket_column', $item );
188
+
189
+ $output = ob_get_clean();
190
+
191
+ return $output;
192
+ }
193
+
194
  /**
195
  * Handler for the check in column
196
  *
199
  * @return string
200
  */
201
  public function column_check_in( $item ) {
202
+ $default_checkin_stati = array();
203
+ $provider = $item['provider_slug'];
204
+ $order_id = $item['order_id'];
205
+
206
+ /**
207
+ * Filters the order stati that will allow for a ticket to be checked in for all commerce providers.
208
+ *
209
+ * @since 4.1
210
+ *
211
+ * @param array $default_checkin_stati An array of default order stati that will make a ticket eligible for check-in.
212
+ * @param string $provider The ticket provider slug.
213
+ * @param int $order_id The order post ID.
214
+ */
215
+ $check_in_stati = apply_filters( 'event_tickets_attendees_checkin_stati', $default_checkin_stati, $provider, $order_id );
216
+
217
+ /**
218
+ * Filters the order stati that will allow for a ticket to be checked in for a specific commerce provider.
219
+ *
220
+ * @since 4.1
221
+ *
222
+ * @param array $default_checkin_stati An array of default order stati that will make a ticket eligible for check-in.
223
+ * @param int $order_id The order post ID.
224
+ */
225
+ $check_in_stati = apply_filters( "event_tickets_attendees_{$provider}_checkin_stati", $check_in_stati, $order_id );
226
+
227
+ if (
228
+ ! empty( $item['order_status'] )
229
+ && ! empty( $item['order_id_link_src'] )
230
+ && is_array( $check_in_stati )
231
+ && ! in_array( $item['order_status'], $check_in_stati )
232
+ ) {
233
+ $button_template = '<a href="%s" class="button-secondary tickets-checkin">%s</a>';
234
+
235
+ return sprintf( $button_template, $item['order_id_link_src'], __( 'View order', 'event-tickets' ) );
236
+ }
237
+
238
  $checkin = sprintf( '<a href="#" data-attendee-id="%d" data-provider="%s" class="button-secondary tickets_checkin">%s</a>', esc_attr( $item['attendee_id'] ), esc_attr( $item['provider'] ), esc_html__( 'Check in', 'event-tickets' ) );
239
  $uncheckin = sprintf( '<span class="delete"><a href="#" data-attendee-id="%d" data-provider="%s" class="tickets_uncheckin">%s</a></span>', esc_attr( $item['attendee_id'] ), esc_attr( $item['provider'] ), esc_html__( 'Undo Check in', 'event-tickets' ) );
240
 
258
  echo '<tr class="' . sanitize_html_class( $row_class ) . esc_attr( $checked ) . '">';
259
  $this->single_row_columns( $item );
260
  echo '</tr>';
 
261
 
262
+ /**
263
+ * Hook to allow for the insertion of data after an attendee table row
264
+ *
265
+ * @var $item Attendee data
266
+ */
267
+ do_action( 'event_tickets_attendees_table_after_row', $item );
268
+ }
269
 
270
  /**
271
  * Extra controls to be displayed between bulk actions and pagination.
330
  return (array) apply_filters( 'tribe_events_tickets_attendees_table_bulk_actions', $actions );
331
  }
332
 
 
333
  /**
334
  * Handler for the different bulk actions
335
  */
src/Tribe/Global_Stock.php ADDED
@@ -0,0 +1,126 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ class Tribe__Tickets__Global_Stock {
3
+ /**
4
+ * Post meta key used to store the global stock flag.
5
+ */
6
+ const GLOBAL_STOCK_ENABLED = '_tribe_ticket_use_global_stock';
7
+
8
+ /**
9
+ * Post meta key used to store the actual global stock level.
10
+ */
11
+ const GLOBAL_STOCK_LEVEL = '_tribe_ticket_global_stock_level';
12
+
13
+ /**
14
+ * Flag used to indicate that a ticket will use the global stock.
15
+ */
16
+ const GLOBAL_STOCK_MODE = 'global';
17
+
18
+ /**
19
+ * Flag used to indicate that a ticket will use the global stock,
20
+ * but that a cap has been placed on the total number of sales for
21
+ * this ticket type.
22
+ */
23
+ const CAPPED_STOCK_MODE = 'capped';
24
+
25
+ /**
26
+ * Flag used to indicate that, if global stock is in effect for
27
+ * an event, the specific ticket this flag is applied to will
28
+ * maintain it's own inventory rather than draw from the global
29
+ * pool.
30
+ */
31
+ const OWN_STOCK_MODE = 'own';
32
+
33
+ /**
34
+ * @var int $post_id
35
+ */
36
+ protected $post_id;
37
+
38
+ /**
39
+ * @param int $post_id
40
+ */
41
+ public function __construct( $post_id ) {
42
+ $this->post_id = absint( $post_id );
43
+ }
44
+
45
+ /**
46
+ * Enables global stock control for the current post.
47
+ *
48
+ * As a convenience, false can be passed to this method to disable rather
49
+ * than enable global stock.
50
+ *
51
+ * @param bool $yes
52
+ */
53
+ public function enable( $yes = true ) {
54
+ update_post_meta( $this->post_id, self::GLOBAL_STOCK_ENABLED, (bool) $yes );
55
+ }
56
+
57
+ /**
58
+ * Disables global stock control for the current post.
59
+ *
60
+ * As a convenience, false can be passed to this method to enable rather
61
+ * than disable global stock.
62
+ *
63
+ * @var bool $yes
64
+ */
65
+ public function disable( $yes = true ) {
66
+ update_post_meta( $this->post_id, self::GLOBAL_STOCK_ENABLED, ! (bool) $yes );
67
+ }
68
+
69
+ /**
70
+ * Indicates if global stock is enabled for this post.
71
+ *
72
+ * @return bool
73
+ */
74
+ public function is_enabled() {
75
+ return (bool) get_post_meta( $this->post_id, self::GLOBAL_STOCK_ENABLED, true );
76
+ }
77
+
78
+ /**
79
+ * Sets the global stock level for the current post.
80
+ *
81
+ * @param int $quantity
82
+ */
83
+ public function set_stock_level( $quantity ) {
84
+ update_post_meta( $this->post_id, self::GLOBAL_STOCK_LEVEL, (int) $quantity );
85
+
86
+ /**
87
+ * Fires when the global stock level is set/changed.
88
+ *
89
+ * @param int $post_id
90
+ * @param int $quantity
91
+ */
92
+ do_action( 'tribe_tickets_global_stock_level_changed', $this->post_id, $quantity );
93
+ }
94
+
95
+ /**
96
+ * Returns the post's global stock.
97
+ *
98
+ * @return int
99
+ */
100
+ public function get_stock_level() {
101
+ return (int) get_post_meta( $this->post_id, self::GLOBAL_STOCK_LEVEL, true );
102
+ }
103
+
104
+ /**
105
+ * Returns a count of the number of global ticket sales for this event.
106
+ *
107
+ * @return int
108
+ */
109
+ public function tickets_sold() {
110
+ $sales = 0;
111
+
112
+ foreach ( Tribe__Tickets__Tickets::get_all_event_tickets( $this->post_id ) as $ticket ) {
113
+ /**
114
+ * @var Tribe__Tickets__Ticket_Object $ticket
115
+ */
116
+ switch ( $ticket->global_stock_mode() ) {
117
+ case self::CAPPED_STOCK_MODE:
118
+ case self::GLOBAL_STOCK_MODE:
119
+ $sales += (int) $ticket->qty_sold();
120
+ break;
121
+ }
122
+ }
123
+
124
+ return $sales;
125
+ }
126
+ }
src/Tribe/Google_Event_Data.php CHANGED
File without changes
src/Tribe/Main.php CHANGED
@@ -9,12 +9,12 @@ class Tribe__Tickets__Main {
9
  /**
10
  * Current version of this plugin
11
  */
12
- const VERSION = '4.0.6';
13
 
14
  /**
15
  * Min required The Events Calendar version
16
  */
17
- const MIN_TEC_VERSION = '4.0.7';
18
 
19
  /**
20
  * Name of the provider
@@ -110,7 +110,11 @@ class Tribe__Tickets__Main {
110
  load_plugin_textdomain( 'event-tickets', false, $this->plugin_dir . 'lang/' );
111
 
112
  $this->hooks();
 
113
  $this->has_initialized = true;
 
 
 
114
  }
115
 
116
  /**
@@ -223,7 +227,20 @@ class Tribe__Tickets__Main {
223
  add_action( 'tribe_help_pre_get_sections', array( $this, 'add_help_section_support_content' ) );
224
  add_action( 'tribe_help_pre_get_sections', array( $this, 'add_help_section_featured_content' ) );
225
  add_action( 'tribe_help_pre_get_sections', array( $this, 'add_help_section_extra_content' ) );
 
226
  add_action( 'plugins_loaded', array( 'Tribe__Support', 'getInstance' ) );
 
 
 
 
 
 
 
 
 
 
 
 
227
  }
228
 
229
  /**
@@ -295,13 +312,27 @@ class Tribe__Tickets__Main {
295
  }
296
  }
297
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
298
  /**
299
  * Hooked to the init action
300
  */
301
  public function init() {
302
- // set up the RSVP object
303
- $this->rsvp();
304
-
305
  // Provide continued support for legacy ticketing modules
306
  $this->legacy_provider_support = new Tribe__Tickets__Legacy_Provider_Support;
307
 
@@ -312,13 +343,7 @@ class Tribe__Tickets__Main {
312
  * rsvp ticket object accessor
313
  */
314
  public function rsvp() {
315
- static $rsvp;
316
-
317
- if ( ! $rsvp ) {
318
- $rsvp = Tribe__Tickets__RSVP::get_instance();
319
- }
320
-
321
- return $rsvp;
322
  }
323
 
324
  /**
9
  /**
10
  * Current version of this plugin
11
  */
12
+ const VERSION = '4.1';
13
 
14
  /**
15
  * Min required The Events Calendar version
16
  */
17
+ const MIN_TEC_VERSION = '3.12.4';
18
 
19
  /**
20
  * Name of the provider
110
  load_plugin_textdomain( 'event-tickets', false, $this->plugin_dir . 'lang/' );
111
 
112
  $this->hooks();
113
+
114
  $this->has_initialized = true;
115
+
116
+ // set up the RSVP object
117
+ $this->rsvp();
118
  }
119
 
120
  /**
227
  add_action( 'tribe_help_pre_get_sections', array( $this, 'add_help_section_support_content' ) );
228
  add_action( 'tribe_help_pre_get_sections', array( $this, 'add_help_section_featured_content' ) );
229
  add_action( 'tribe_help_pre_get_sections', array( $this, 'add_help_section_extra_content' ) );
230
+ add_filter( 'tribe_support_registered_template_systems', array( $this, 'add_template_updates_check' ) );
231
  add_action( 'plugins_loaded', array( 'Tribe__Support', 'getInstance' ) );
232
+ add_action( 'tribe_events_single_event_after_the_meta', array( $this, 'add_linking_archor' ), 5 );
233
+
234
+ }
235
+
236
+ /**
237
+ * Add an Anchor for users to be able to link to
238
+ * The height is to make sure it links on all browsers
239
+ *
240
+ * @return void
241
+ */
242
+ public function add_linking_archor() {
243
+ echo '<div id="buy-tickets" style="height: 1px;"></div>';
244
  }
245
 
246
  /**
312
  }
313
  }
314
 
315
+ /**
316
+ * Register Event Tickets with the template update checker.
317
+ *
318
+ * @param array $plugins
319
+ *
320
+ * @return array
321
+ */
322
+ public function add_template_updates_check( $plugins ) {
323
+ $plugins[ __( 'Event Tickets', 'event-tickets' ) ] = array(
324
+ self::VERSION,
325
+ $this->plugin_path . 'src/views/tickets',
326
+ trailingslashit( get_stylesheet_directory() ) . 'tribe-events/tickets'
327
+ );
328
+
329
+ return $plugins;
330
+ }
331
+
332
  /**
333
  * Hooked to the init action
334
  */
335
  public function init() {
 
 
 
336
  // Provide continued support for legacy ticketing modules
337
  $this->legacy_provider_support = new Tribe__Tickets__Legacy_Provider_Support;
338
 
343
  * rsvp ticket object accessor
344
  */
345
  public function rsvp() {
346
+ return Tribe__Tickets__RSVP::get_instance();
 
 
 
 
 
 
347
  }
348
 
349
  /**
src/Tribe/Metabox.php CHANGED
@@ -81,7 +81,11 @@ class Tribe__Tickets__Metabox {
81
  'title' => esc_html__( 'Ticket header image', 'event-tickets' ),
82
  'button' => esc_html__( 'Set as ticket header', 'event-tickets' ),
83
  );
 
84
  wp_localize_script( 'event-tickets', 'HeaderImageData', $upload_header_data );
 
 
 
85
 
86
 
87
  $nonces = array(
81
  'title' => esc_html__( 'Ticket header image', 'event-tickets' ),
82
  'button' => esc_html__( 'Set as ticket header', 'event-tickets' ),
83
  );
84
+
85
  wp_localize_script( 'event-tickets', 'HeaderImageData', $upload_header_data );
86
+ wp_localize_script( 'event-tickets', 'tribe_global_stock_admin_ui', array(
87
+ 'nav_away_msg' => __( 'It looks like you have modified your global stock settings but have not saved or updated the post.', 'event-tickets' ),
88
+ ) );
89
 
90
 
91
  $nonces = array(
src/Tribe/RSVP.php CHANGED
@@ -60,6 +60,13 @@ class Tribe__Tickets__RSVP extends Tribe__Tickets__Tickets {
60
  */
61
  public $security_code = '_tribe_rsvp_security_code';
62
 
 
 
 
 
 
 
 
63
  /**
64
  * Meta key that holds the full name of the tickets RSVP "buyer"
65
  *
@@ -103,8 +110,8 @@ class Tribe__Tickets__RSVP extends Tribe__Tickets__Tickets {
103
  * @return Tribe__Tickets__RSVP
104
  */
105
  public static function get_instance() {
106
- if ( ! is_a( self::$instance, __CLASS__ ) ) {
107
- self::$instance = new self();
108
  }
109
 
110
  return self::$instance;
@@ -123,8 +130,7 @@ class Tribe__Tickets__RSVP extends Tribe__Tickets__Tickets {
123
 
124
  parent::__construct();
125
 
126
- $this->init();
127
- $this->hooks();
128
  }
129
 
130
  /**
@@ -242,7 +248,6 @@ class Tribe__Tickets__RSVP extends Tribe__Tickets__Tickets {
242
  * Generate and store all the attendees information for a new order.
243
  */
244
  public function generate_tickets( ) {
245
-
246
  if ( empty( $_POST['tickets_process'] ) || empty( $_POST['attendee'] ) || empty( $_POST['product_id'] ) ) {
247
  return;
248
  }
@@ -254,6 +259,7 @@ class Tribe__Tickets__RSVP extends Tribe__Tickets__Tickets {
254
  $attendee_email = empty( $_POST['attendee']['email'] ) ? null : sanitize_email( $_POST['attendee']['email'] );
255
  $attendee_email = is_email( $attendee_email ) ? $attendee_email : null;
256
  $attendee_full_name = empty( $_POST['attendee']['full_name'] ) ? null : sanitize_text_field( $_POST['attendee']['full_name'] );
 
257
 
258
  if ( ! $attendee_email || ! $attendee_full_name ) {
259
  $url = get_permalink( $event_id );
@@ -264,6 +270,7 @@ class Tribe__Tickets__RSVP extends Tribe__Tickets__Tickets {
264
 
265
  // Iterate over each product
266
  foreach ( (array) $_POST['product_id'] as $product_id ) {
 
267
 
268
  // Get the event this tickets is for
269
  $event_id = get_post_meta( $product_id, $this->event_key, true );
@@ -310,10 +317,40 @@ class Tribe__Tickets__RSVP extends Tribe__Tickets__Tickets {
310
  update_post_meta( $attendee_id, self::ATTENDEE_EVENT_KEY, $event_id );
311
  update_post_meta( $attendee_id, $this->security_code, $this->generate_security_code( $attendee_id ) );
312
  update_post_meta( $attendee_id, $this->order_key, $order_id );
 
313
  update_post_meta( $attendee_id, $this->full_name, $attendee_full_name );
314
  update_post_meta( $attendee_id, $this->email, $attendee_email );
 
 
 
 
 
 
 
 
 
 
 
 
315
  }
 
 
 
 
 
 
 
 
 
316
  }
 
 
 
 
 
 
 
 
317
  if ( $has_tickets ) {
318
  $this->send_tickets_email( $order_id ) ;
319
  }
@@ -361,14 +398,18 @@ class Tribe__Tickets__RSVP extends Tribe__Tickets__Tickets {
361
  ) );
362
 
363
  foreach ( $query->posts as $post ) {
 
 
364
  $attendees[] = array(
365
  'event_id' => get_post_meta( $post->ID, self::ATTENDEE_EVENT_KEY, true ),
366
- 'ticket_name' => get_post( get_post_meta( $post->ID, self::ATTENDEE_PRODUCT_KEY, true ) )->post_title,
 
367
  'holder_name' => get_post_meta( $post->ID, $this->full_name, true ),
368
  'holder_email' => get_post_meta( $post->ID, $this->email, true ),
369
  'order_id' => $order_id,
370
  'ticket_id' => $post->ID,
371
  'security_code' => get_post_meta( $post->ID, $this->security_code, true ),
 
372
  );
373
  }
374
 
@@ -397,8 +438,12 @@ class Tribe__Tickets__RSVP extends Tribe__Tickets__Tickets {
397
  * @return bool
398
  */
399
  public function save_ticket( $event_id, $ticket, $raw_data = array() ) {
 
 
400
 
401
  if ( empty( $ticket->ID ) ) {
 
 
402
  /* Create main product post */
403
  $args = array(
404
  'post_status' => 'publish',
@@ -450,6 +495,26 @@ class Tribe__Tickets__RSVP extends Tribe__Tickets__Tickets {
450
  delete_post_meta( $ticket->ID, '_ticket_end_date' );
451
  }
452
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
453
  return true;
454
  }
455
 
@@ -620,14 +685,14 @@ class Tribe__Tickets__RSVP extends Tribe__Tickets__Tickets {
620
  return false;
621
  }
622
 
623
- $event = get_post_meta( $ticket_product, $this->event_key, true );
624
 
625
- if ( ! $event && '' === ( $event = get_post_meta( $ticket_product, self::ATTENDEE_EVENT_KEY, true ) ) ) {
626
  return false;
627
  }
628
 
629
- if ( in_array( get_post_type( $event ), Tribe__Tickets__Main::instance()->post_types() ) ) {
630
- return get_post( $event );
631
  }
632
 
633
  return false;
@@ -641,6 +706,7 @@ class Tribe__Tickets__RSVP extends Tribe__Tickets__Tickets {
641
  * order_status
642
  * purchaser_name
643
  * purchaser_email
 
644
  * ticket
645
  * attendee_id
646
  * security
@@ -674,6 +740,7 @@ class Tribe__Tickets__RSVP extends Tribe__Tickets__Tickets {
674
  $product_id = get_post_meta( $attendee->ID, self::ATTENDEE_PRODUCT_KEY, true );
675
  $name = get_post_meta( $attendee->ID, $this->full_name, true );
676
  $email = get_post_meta( $attendee->ID, $this->email, true );
 
677
 
678
  if ( empty( $product_id ) ) {
679
  continue;
@@ -687,12 +754,14 @@ class Tribe__Tickets__RSVP extends Tribe__Tickets__Tickets {
687
  'order_id' => $attendee->ID,
688
  'purchaser_name' => $name,
689
  'purchaser_email' => $email,
 
690
  'ticket' => $product_title,
691
  'attendee_id' => $attendee->ID,
692
  'security' => $security,
693
  'product_id' => $product_id,
694
  'check_in' => $checkin,
695
  'provider' => __CLASS__,
 
696
  );
697
  }
698
 
@@ -702,16 +771,33 @@ class Tribe__Tickets__RSVP extends Tribe__Tickets__Tickets {
702
  /**
703
  * Marks an attendee as checked in for an event
704
  *
 
 
 
 
 
 
 
705
  * @param $attendee_id
706
- * @param $qr true if from QR checkin process
707
  *
708
  * @return bool
709
  */
710
- public function checkin( $attendee_id, $qr = null ) {
 
 
711
  update_post_meta( $attendee_id, $this->checkin_key, 1 );
712
- if ( ! $qr ) {
 
713
  update_post_meta( $attendee_id, '_tribe_qr_status', 1 );
714
  }
 
 
 
 
 
 
 
715
  do_action( 'rsvp_checkin', $attendee_id, $qr );
716
 
717
  return true;
60
  */
61
  public $security_code = '_tribe_rsvp_security_code';
62
 
63
+ /**
64
+ * Meta key that if this attendee wants to show on the attendee list
65
+ *
66
+ * @var string
67
+ */
68
+ const ATTENDEE_OPTOUT_KEY = '_tribe_rsvp_attendee_optout';
69
+
70
  /**
71
  * Meta key that holds the full name of the tickets RSVP "buyer"
72
  *
110
  * @return Tribe__Tickets__RSVP
111
  */
112
  public static function get_instance() {
113
+ if ( ! self::$instance ) {
114
+ self::$instance = new self;
115
  }
116
 
117
  return self::$instance;
130
 
131
  parent::__construct();
132
 
133
+ add_action( 'init', array( $this, 'init' ) );
 
134
  }
135
 
136
  /**
248
  * Generate and store all the attendees information for a new order.
249
  */
250
  public function generate_tickets( ) {
 
251
  if ( empty( $_POST['tickets_process'] ) || empty( $_POST['attendee'] ) || empty( $_POST['product_id'] ) ) {
252
  return;
253
  }
259
  $attendee_email = empty( $_POST['attendee']['email'] ) ? null : sanitize_email( $_POST['attendee']['email'] );
260
  $attendee_email = is_email( $attendee_email ) ? $attendee_email : null;
261
  $attendee_full_name = empty( $_POST['attendee']['full_name'] ) ? null : sanitize_text_field( $_POST['attendee']['full_name'] );
262
+ $attendee_optout = empty( $_POST['attendee']['optout'] ) ? false : (bool) $_POST['attendee']['optout'];
263
 
264
  if ( ! $attendee_email || ! $attendee_full_name ) {
265
  $url = get_permalink( $event_id );
270
 
271
  // Iterate over each product
272
  foreach ( (array) $_POST['product_id'] as $product_id ) {
273
+ $order_attendee_id = 0;
274
 
275
  // Get the event this tickets is for
276
  $event_id = get_post_meta( $product_id, $this->event_key, true );
317
  update_post_meta( $attendee_id, self::ATTENDEE_EVENT_KEY, $event_id );
318
  update_post_meta( $attendee_id, $this->security_code, $this->generate_security_code( $attendee_id ) );
319
  update_post_meta( $attendee_id, $this->order_key, $order_id );
320
+ update_post_meta( $attendee_id, self::ATTENDEE_OPTOUT_KEY, (bool) $attendee_optout );
321
  update_post_meta( $attendee_id, $this->full_name, $attendee_full_name );
322
  update_post_meta( $attendee_id, $this->email, $attendee_email );
323
+
324
+ /**
325
+ * Action fired when an RSVP attendee ticket is created
326
+ *
327
+ * @var $attendee_id ID of the attendee post
328
+ * @var $event_id Event post ID
329
+ * @var $product_id RSVP ticket post ID
330
+ * @var $order_attendee_id Attendee # for order
331
+ */
332
+ do_action( 'event_tickets_rsvp_ticket_created', $attendee_id, $event_id, $product_id, $order_attendee_id );
333
+
334
+ $order_attendee_id++;
335
  }
336
+
337
+ /**
338
+ * Action fired when an RSVP has had attendee tickets generated for it
339
+ *
340
+ * @var $product_id RSVP ticket post ID
341
+ * @var $order_id ID of the RSVP order
342
+ * @var $qty Quantity ordered
343
+ */
344
+ do_action( 'event_tickets_rsvp_tickets_generated_for_product', $product_id, $order_id, $qty );
345
  }
346
+
347
+ /**
348
+ * Action fired when an RSVP attendee tickets have been generated
349
+ *
350
+ * @var $order_id ID of the RSVP order
351
+ */
352
+ do_action( 'event_tickets_rsvp_tickets_generated', $order_id );
353
+
354
  if ( $has_tickets ) {
355
  $this->send_tickets_email( $order_id ) ;
356
  }
398
  ) );
399
 
400
  foreach ( $query->posts as $post ) {
401
+ $product = get_post( get_post_meta( $post->ID, self::ATTENDEE_PRODUCT_KEY, true ) );
402
+
403
  $attendees[] = array(
404
  'event_id' => get_post_meta( $post->ID, self::ATTENDEE_EVENT_KEY, true ),
405
+ 'product_id' => $product->ID,
406
+ 'ticket_name' => $product->post_title,
407
  'holder_name' => get_post_meta( $post->ID, $this->full_name, true ),
408
  'holder_email' => get_post_meta( $post->ID, $this->email, true ),
409
  'order_id' => $order_id,
410
  'ticket_id' => $post->ID,
411
  'security_code' => get_post_meta( $post->ID, $this->security_code, true ),
412
+ 'optout' => (bool) get_post_meta( $post->ID, self::ATTENDEE_OPTOUT_KEY, true ),
413
  );
414
  }
415
 
438
  * @return bool
439
  */
440
  public function save_ticket( $event_id, $ticket, $raw_data = array() ) {
441
+ // assume we are updating until we find out otherwise
442
+ $save_type = 'update';
443
 
444
  if ( empty( $ticket->ID ) ) {
445
+ $save_type = 'create';
446
+
447
  /* Create main product post */
448
  $args = array(
449
  'post_status' => 'publish',
495
  delete_post_meta( $ticket->ID, '_ticket_end_date' );
496
  }
497
 
498
+ /**
499
+ * Generic action fired after saving a ticket (by type)
500
+ *
501
+ * @var int Post ID of post the ticket is tied to
502
+ * @var Tribe__Tickets__Ticket_Object Ticket that was just saved
503
+ * @var array Ticket data
504
+ * @var string Commerce engine class
505
+ */
506
+ do_action( 'event_tickets_after_' . $save_type . '_ticket', $event_id, $ticket, $raw_data, __CLASS__ );
507
+
508
+ /**
509
+ * Generic action fired after saving a ticket
510
+ *
511
+ * @var int Post ID of post the ticket is tied to
512
+ * @var Tribe__Tickets__Ticket_Object Ticket that was just saved
513
+ * @var array Ticket data
514
+ * @var string Commerce engine class
515
+ */
516
+ do_action( 'event_tickets_after_save_ticket', $event_id, $ticket, $raw_data, __CLASS__ );
517
+
518
  return true;
519
  }
520
 
685
  return false;
686
  }
687
 
688
+ $event_id = get_post_meta( $ticket_product, $this->event_key, true );
689
 
690
+ if ( ! $event_id && '' === ( $event_id = get_post_meta( $ticket_product, self::ATTENDEE_EVENT_KEY, true ) ) ) {
691
  return false;
692
  }
693
 
694
+ if ( in_array( get_post_type( $event_id ), Tribe__Tickets__Main::instance()->post_types() ) ) {
695
+ return get_post( $event_id );
696
  }
697
 
698
  return false;
706
  * order_status
707
  * purchaser_name
708
  * purchaser_email
709
+ * optout
710
  * ticket
711
  * attendee_id
712
  * security
740
  $product_id = get_post_meta( $attendee->ID, self::ATTENDEE_PRODUCT_KEY, true );
741
  $name = get_post_meta( $attendee->ID, $this->full_name, true );
742
  $email = get_post_meta( $attendee->ID, $this->email, true );
743
+ $optout = (bool) get_post_meta( $attendee->ID, self::ATTENDEE_OPTOUT_KEY, true );
744
 
745
  if ( empty( $product_id ) ) {
746
  continue;
754
  'order_id' => $attendee->ID,
755
  'purchaser_name' => $name,
756
  'purchaser_email' => $email,
757
+ 'optout' => $optout,
758
  'ticket' => $product_title,
759
  'attendee_id' => $attendee->ID,
760
  'security' => $security,
761
  'product_id' => $product_id,
762
  'check_in' => $checkin,
763
  'provider' => __CLASS__,
764
+ 'provider_slug' => 'rsvp',
765
  );
766
  }
767
 
771
  /**
772
  * Marks an attendee as checked in for an event
773
  *
774
+ * Because we must still support our legacy ticket plugins, we cannot change the abstract
775
+ * checkin() method's signature. However, the QR checkin process needs to move forward
776
+ * so we get around that problem by leveraging func_get_arg() to pass a second argument.
777
+ *
778
+ * It is hacky, but we'll aim to resolve this issue when we end-of-life our legacy ticket plugins
779
+ * OR write around it in a future major release
780
+ *
781
  * @param $attendee_id
782
+ * @param $qr true if from QR checkin process (NOTE: this is a param-less parameter for backward compatibility)
783
  *
784
  * @return bool
785
  */
786
+ public function checkin( $attendee_id ) {
787
+ $qr = null;
788
+
789
  update_post_meta( $attendee_id, $this->checkin_key, 1 );
790
+
791
+ if ( func_num_args() > 1 && $qr = func_get_arg( 1 ) ) {
792
  update_post_meta( $attendee_id, '_tribe_qr_status', 1 );
793
  }
794
+
795
+ /**
796
+ * Fires a checkin action
797
+ *
798
+ * @var int $attendee_id
799
+ * @var bool|null $qr
800
+ */
801
  do_action( 'rsvp_checkin', $attendee_id, $qr );
802
 
803
  return true;
src/Tribe/Ticket_Object.php CHANGED
@@ -71,6 +71,11 @@ if ( ! class_exists( 'Tribe__Tickets__Ticket_Object' ) ) {
71
  */
72
  public $provider_class;
73
 
 
 
 
 
 
74
  /**
75
  * Amount of tickets of this kind in stock
76
  * Use $this->stock( value ) to set manage and get the value
@@ -79,6 +84,22 @@ if ( ! class_exists( 'Tribe__Tickets__Ticket_Object' ) ) {
79
  */
80
  protected $stock = 0;
81
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
82
  /**
83
  * Amount of tickets of this kind sold
84
  * Use $this->qty_sold( value ) to set manage and get the value
@@ -227,6 +248,11 @@ if ( ! class_exists( 'Tribe__Tickets__Ticket_Object' ) ) {
227
  // Do the math!
228
  $remaining = $this->original_stock() - $this->qty_sold() - $this->qty_pending();
229
 
 
 
 
 
 
230
  // Prevents Negative
231
  return max( $remaining, 0 );
232
  }
@@ -271,6 +297,43 @@ if ( ! class_exists( 'Tribe__Tickets__Ticket_Object' ) ) {
271
  return $this->stock;
272
  }
273
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
274
  /**
275
  * Method to manage the protected `qty_sold` propriety of the Object
276
  * Prevents setting `qty_sold` lower then zero
@@ -380,6 +443,41 @@ if ( ! class_exists( 'Tribe__Tickets__Ticket_Object' ) ) {
380
  // return the new Qty Cancelled
381
  return $this->qty_cancelled;
382
  }
383
- }
384
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
385
  }
71
  */
72
  public $provider_class;
73
 
74
+ /**
75
+ * @var Tribe__Tickets__Tickets
76
+ */
77
+ protected $provider;
78
+
79
  /**
80
  * Amount of tickets of this kind in stock
81
  * Use $this->stock( value ) to set manage and get the value
84
  */
85
  protected $stock = 0;
86
 
87
+ /**
88
+ * The mode of stock handling to be used for the ticket when global stock
89
+ * is enabled for the event.
90
+ *
91
+ * @var string
92
+ */
93
+ protected $global_stock_mode = Tribe__Tickets__Global_Stock::OWN_STOCK_MODE;
94
+
95
+ /**
96
+ * The maximum permitted number of sales for this ticket when global stock
97
+ * is enabled for the event and CAPPED_STOCK_MODE is in effect.
98
+ *
99
+ * @var int
100
+ */
101
+ protected $global_stock_cap = 0;
102
+
103
  /**
104
  * Amount of tickets of this kind sold
105
  * Use $this->qty_sold( value ) to set manage and get the value
248
  // Do the math!
249
  $remaining = $this->original_stock() - $this->qty_sold() - $this->qty_pending();
250
 
251
+ // Adjust if using global stock with a sales cap
252
+ if ( Tribe__Tickets__Global_Stock::CAPPED_STOCK_MODE === $this->global_stock_mode() ) {
253
+ $remaining = min( $remaining, $this->global_stock_cap() );
254
+ }
255
+
256
  // Prevents Negative
257
  return max( $remaining, 0 );
258
  }
297
  return $this->stock;
298
  }
299
 
300
+ /**
301
+ * Sets or gets the current global stock mode in effect for the ticket.
302
+ *
303
+ * Typically this is one of the constants provided by Tribe__Tickets__Global_Stock:
304
+ *
305
+ * GLOBAL_STOCK_MODE if it should draw on the global stock
306
+ * CAPPED_STOCK_MODE as above but with a limit on the total number of allowed sales
307
+ * OWN_STOCK_MODE if it should behave as if global stock is not in effect
308
+ *
309
+ * @param string $mode
310
+ *
311
+ * @return string
312
+ */
313
+ public function global_stock_mode( $mode = null ) {
314
+ if ( is_string( $mode ) ) {
315
+ $this->global_stock_mode = $mode;
316
+ }
317
+
318
+ return $this->global_stock_mode;
319
+ }
320
+
321
+ /**
322
+ * Sets or gets any cap on sales that might be in effect for this ticket when global stock
323
+ * mode is in effect.
324
+ *
325
+ * @param int $cap
326
+ *
327
+ * @return int
328
+ */
329
+ public function global_stock_cap( $cap = null ) {
330
+ if ( is_numeric( $cap ) ) {
331
+ $this->global_stock_cap = (int) $cap;
332
+ }
333
+
334
+ return (int) $this->global_stock_cap;
335
+ }
336
+
337
  /**
338
  * Method to manage the protected `qty_sold` propriety of the Object
339
  * Prevents setting `qty_sold` lower then zero
443
  // return the new Qty Cancelled
444
  return $this->qty_cancelled;
445
  }
 
446
 
447
+ /**
448
+ * Returns an instance of the provider class.
449
+ *
450
+ * @return Tribe__Tickets__Tickets|null
451
+ */
452
+ public function get_provider() {
453
+ if ( empty( $this->provider ) ) {
454
+ if ( empty( $this->provider_class ) || ! class_exists( $this->provider_class ) ) {
455
+ return null;
456
+ }
457
+
458
+ if ( method_exists( $this->provider_class, 'get_instance' ) ) {
459
+ $this->provider = call_user_func( array( $this->provider_class, 'get_instance' ) );
460
+ } else {
461
+ $this->provider = new $this->provider_class;
462
+ }
463
+ }
464
+
465
+ return $this->provider;
466
+ }
467
+
468
+ /**
469
+ * Returns the ID of the event post this ticket belongs to.
470
+ *
471
+ * @return WP_Post|null
472
+ */
473
+ public function get_event() {
474
+ $provider = $this->get_provider();
475
+
476
+ if ( null !== $provider ) {
477
+ return $provider->get_event_for_ticket( $this->ID );
478
+ }
479
+
480
+ return null;
481
+ }
482
+ }
483
  }
src/Tribe/Tickets.php CHANGED
@@ -30,6 +30,21 @@ if ( ! class_exists( 'Tribe__Tickets__Tickets' ) ) {
30
  */
31
  protected static $active_modules = array();
32
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
33
  /**
34
  * Name of this class. Note that it refers to the child class.
35
  * @var string
@@ -69,6 +84,11 @@ if ( ! class_exists( 'Tribe__Tickets__Tickets' ) ) {
69
  */
70
  protected $pluginUrl;
71
 
 
 
 
 
 
72
  /**
73
  * Returns link to the report interface for sales for an event or
74
  * null if the provider doesn't have reporting capabilities.
@@ -181,7 +201,7 @@ if ( ! class_exists( 'Tribe__Tickets__Tickets' ) ) {
181
  *
182
  * @return mixed
183
  */
184
- abstract public function checkin( $attendee_id, $qr );
185
 
186
  /**
187
  * Mark an attendee as not checked in
@@ -232,6 +252,18 @@ if ( ! class_exists( 'Tribe__Tickets__Tickets' ) ) {
232
  return '';
233
  }
234
 
 
 
 
 
 
 
 
 
 
 
 
 
235
  /**
236
  * Returns instance of the child class (singleton)
237
  *
@@ -344,6 +376,15 @@ if ( ! class_exists( 'Tribe__Tickets__Tickets' ) ) {
344
  do_action( 'tribe_tickets_ticket_added', $post_id );
345
  }
346
 
 
 
 
 
 
 
 
 
 
347
  $this->ajax_ok( $return );
348
  }
349
 
@@ -381,6 +422,15 @@ if ( ! class_exists( 'Tribe__Tickets__Tickets' ) ) {
381
 
382
  $ticket->provider_class = $this->className;
383
 
 
 
 
 
 
 
 
 
 
384
  // Pass the control to the child object
385
  return $this->save_ticket( $post_id, $ticket, $data );
386
  }
@@ -540,7 +590,13 @@ if ( ! class_exists( 'Tribe__Tickets__Tickets' ) ) {
540
  $return['description'] = htmlspecialchars_decode( $return['description'] );
541
 
542
  ob_start();
543
- $this->do_metabox_advanced_options( $post_id, $ticket_id );
 
 
 
 
 
 
544
  $extra = ob_get_contents();
545
  ob_end_clean();
546
 
@@ -586,15 +642,44 @@ if ( ! class_exists( 'Tribe__Tickets__Tickets' ) ) {
586
  */
587
  public static function get_event_attendees( $event_id ) {
588
  $attendees = array();
 
 
 
 
 
 
 
 
 
 
 
 
589
 
590
  foreach ( self::$active_modules as $class => $module ) {
591
  $obj = call_user_func( array( $class, 'get_instance' ) );
592
  $attendees = array_merge( $attendees, $obj->get_attendees( $event_id ) );
593
  }
594
 
 
 
 
 
 
595
  return $attendees;
596
  }
597
 
 
 
 
 
 
 
 
 
 
 
 
 
598
  /**
599
  * Returns all tickets for an event (all providers are queried for this information).
600
  *
@@ -671,6 +756,23 @@ if ( ! class_exists( 'Tribe__Tickets__Tickets' ) ) {
671
 
672
  // start Helpers
673
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
674
  /**
675
  * Returns whether a class name is a valid active module/provider.
676
  *
@@ -689,6 +791,106 @@ if ( ! class_exists( 'Tribe__Tickets__Tickets' ) ) {
689
  echo 'ticket_advanced ticket_advanced_' . $this->className;
690
  }
691
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
692
  /**
693
  * Returns the array of active modules/providers.
694
  *
30
  */
31
  protected static $active_modules = array();
32
 
33
+ /**
34
+ * Indicates if the frontend ticket form script has already been enqueued (or not).
35
+ *
36
+ * @var bool
37
+ */
38
+ protected static $frontend_script_enqueued = false;
39
+
40
+ /**
41
+ * Collection of ticket objects for which we wish to make global stock data available
42
+ * on the frontend.
43
+ *
44
+ * @var array
45
+ */
46
+ protected static $frontend_ticket_data = array();
47
+
48
  /**
49
  * Name of this class. Note that it refers to the child class.
50
  * @var string
84
  */
85
  protected $pluginUrl;
86
 
87
+ /**
88
+ * Constant with the Transient Key for Attendees Cache
89
+ */
90
+ const ATTENDEES_CACHE = 'tribe_attendees';
91
+
92
  /**
93
  * Returns link to the report interface for sales for an event or
94
  * null if the provider doesn't have reporting capabilities.
201
  *
202
  * @return mixed
203
  */
204
+ abstract public function checkin( $attendee_id );
205
 
206
  /**
207
  * Mark an attendee as not checked in
252
  return '';
253
  }
254
 
255
+ /**
256
+ * Indicates if the module/ticket provider supports a concept of global stock.
257
+ *
258
+ * For backward compatibility reasons this method has not been declared abstract but
259
+ * implementaions are still expected to override it.
260
+ *
261
+ * @return bool
262
+ */
263
+ public function supports_global_stock() {
264
+ return false;
265
+ }
266
+
267
  /**
268
  * Returns instance of the child class (singleton)
269
  *
376
  do_action( 'tribe_tickets_ticket_added', $post_id );
377
  }
378
 
379
+ $return = array( 'html' => $return );
380
+
381
+ /**
382
+ * Filters the return data for ticket add
383
+ *
384
+ * @var array Array of data to return to the ajax call
385
+ */
386
+ $return = apply_filters( 'event_tickets_ajax_ticket_add_data', $return, $post_id );
387
+
388
  $this->ajax_ok( $return );
389
  }
390
 
422
 
423
  $ticket->provider_class = $this->className;
424
 
425
+ /**
426
+ * Fired once a ticket has been created and added to a post
427
+ *
428
+ * @var $post_id Post ID
429
+ * @var $ticket Ticket object
430
+ * @var $data Submitted post data
431
+ */
432
+ do_action( 'tribe_tickets_ticket_add', $post_id, $ticket, $data );
433
+
434
  // Pass the control to the child object
435
  return $this->save_ticket( $post_id, $ticket, $data );
436
  }
590
  $return['description'] = htmlspecialchars_decode( $return['description'] );
591
 
592
  ob_start();
593
+ /**
594
+ * Fired to allow for the insertion of extra form data in the ticket admin form
595
+ *
596
+ * @var $post_id Post ID
597
+ * @var $ticket_id Ticket ID
598
+ */
599
+ do_action( 'tribe_events_tickets_metabox_advanced', $post_id, $ticket_id );
600
  $extra = ob_get_contents();
601
  ob_end_clean();
602
 
642
  */
643
  public static function get_event_attendees( $event_id ) {
644
  $attendees = array();
645
+ if ( ! is_admin() ) {
646
+ $post_transient = Tribe__Post_Transient::instance();
647
+
648
+ $attendees = $post_transient->get( $event_id, self::ATTENDEES_CACHE );
649
+ if ( ! $attendees ) {
650
+ $attendees = array();
651
+ }
652
+
653
+ if ( is_array( $attendees ) && count( $attendees ) > 0 ) {
654
+ return $attendees;
655
+ }
656
+ }
657
 
658
  foreach ( self::$active_modules as $class => $module ) {
659
  $obj = call_user_func( array( $class, 'get_instance' ) );
660
  $attendees = array_merge( $attendees, $obj->get_attendees( $event_id ) );
661
  }
662
 
663
+ if ( ! is_admin() ) {
664
+ $expire = apply_filters( 'tribe_tickets_attendees_expire', HOUR_IN_SECONDS );
665
+ $post_transient->set( $event_id, self::ATTENDEES_CACHE, $attendees, $expire );
666
+ }
667
+
668
  return $attendees;
669
  }
670
 
671
+ /**
672
+ * Returns the total number of attendees for an event (regardless of provider).
673
+ *
674
+ * @param int $event_id
675
+ *
676
+ * @return int
677
+ */
678
+ public static function get_event_attendees_count( $event_id ) {
679
+ $attendees = self::get_event_attendees( $event_id );
680
+ return count( $attendees );
681
+ }
682
+
683
  /**
684
  * Returns all tickets for an event (all providers are queried for this information).
685
  *
756
 
757
  // start Helpers
758
 
759
+ /**
760
+ * Indicates if any of the currently available providers support global stock.
761
+ *
762
+ * @return bool
763
+ */
764
+ public static function global_stock_available() {
765
+ foreach ( self::$active_modules as $class => $module ) {
766
+ $provider = call_user_func( array( $class, 'get_instance' ) );
767
+
768
+ if ( method_exists( $provider, 'supports_global_stock' ) && $provider->supports_global_stock() ) {
769
+ return true;
770
+ }
771
+ }
772
+
773
+ return false;
774
+ }
775
+
776
  /**
777
  * Returns whether a class name is a valid active module/provider.
778
  *
791
  echo 'ticket_advanced ticket_advanced_' . $this->className;
792
  }
793
 
794
+ /**
795
+ * Generates a select element listing the available global stock mode options.
796
+ *
797
+ * @param string $current_option
798
+ *
799
+ * @return string
800
+ */
801
+ protected function global_stock_mode_selector( $current_option = '' ) {
802
+ $output = "<select id='ticket_global_stock' name='ticket_global_stock' class='ticket_field'>\n";
803
+
804
+ // Default to using own stock unless the user explicitly specifies otherwise (important
805
+ // to avoid assuming global stock mode if global stock is enabled/disabled accidentally etc)
806
+ if ( empty( $current_option ) ) {
807
+ $current_option = Tribe__Tickets__Global_Stock::OWN_STOCK_MODE;
808
+ }
809
+
810
+ foreach ( $this->global_stock_mode_options() as $identifier => $name ) {
811
+ $identifier = esc_html( $identifier );
812
+ $name = esc_html( $name );
813
+ $selected = selected( $identifier === $current_option, true, false );
814
+ $output .= "\t<option value='$identifier' $selected> $name </option>\n";
815
+ }
816
+
817
+ return "$output</select>";
818
+ }
819
+
820
+ /**
821
+ * Returns an array of standard global stock mode options that can be
822
+ * reused by implementations.
823
+ *
824
+ * Format is: [ 'identifier' => 'Localized name', ... ]
825
+ *
826
+ * @return array
827
+ */
828
+ protected function global_stock_mode_options() {
829
+ return array(
830
+ Tribe__Tickets__Global_Stock::GLOBAL_STOCK_MODE => __( 'Use global stock', 'event-tickets' ),
831
+ Tribe__Tickets__Global_Stock::CAPPED_STOCK_MODE => __( 'Use global stock but cap sales', 'event-tickets' ),
832
+ Tribe__Tickets__Global_Stock::OWN_STOCK_MODE => __( 'Independent (do not use global stock)', 'event-tickets' ),
833
+ );
834
+ }
835
+
836
+ /**
837
+ * Tries to make data about global stock levels and global stock-enabled ticket objects
838
+ * available to frontend scripts.
839
+ *
840
+ * @param array $tickets
841
+ */
842
+ public static function add_frontend_stock_data( array $tickets ) {
843
+ // Add the frontend ticket form script as needed (we do this lazily since right now
844
+ // it's only required for certain combinations of event/ticket
845
+ if ( ! self::$frontend_script_enqueued ) {
846
+ $url = Tribe__Tickets__Main::instance()->plugin_url . 'src/resources/js/frontend-ticket-form.js';
847
+ $url = Tribe__Template_Factory::getMinFile( $url, true );
848
+
849
+ wp_enqueue_script( 'tribe_tickets_frontend_tickets', $url, array( 'jquery' ), Tribe__Tickets__Main::VERSION, true );
850
+ add_action( 'wp_footer', array( __CLASS__, 'enqueue_frontend_stock_data' ), 1 );
851
+ }
852
+
853
+ self::$frontend_ticket_data += $tickets;
854
+ }
855
+
856
+ /**
857
+ * Takes any global stock data and makes it available via a wp_localize_script() call.
858
+ */
859
+ public static function enqueue_frontend_stock_data() {
860
+ $data = array(
861
+ 'tickets' => array(),
862
+ 'events' => array(),
863
+ );
864
+
865
+ foreach ( self::$frontend_ticket_data as $ticket ) {
866
+ /**
867
+ * @var Tribe__Tickets__Ticket_Object $ticket
868
+ */
869
+ $event_id = $ticket->get_event()->ID;
870
+ $global_stock = new Tribe__Tickets__Global_Stock( $event_id );
871
+ $stock_mode = $ticket->global_stock_mode();
872
+
873
+ $data[ 'tickets' ][ $ticket->ID ] = array(
874
+ 'event_id' => $event_id,
875
+ 'mode' => $stock_mode,
876
+ );
877
+
878
+ if ( Tribe__Tickets__Global_Stock::CAPPED_STOCK_MODE === $stock_mode ) {
879
+ $data[ 'tickets' ][ $ticket->ID ][ 'cap' ] = $ticket->global_stock_cap();
880
+ }
881
+
882
+ if ( Tribe__Tickets__Global_Stock::OWN_STOCK_MODE === $stock_mode ) {
883
+ $data[ 'tickets' ][ $ticket->ID ][ 'stock' ] = $ticket->stock();
884
+ }
885
+
886
+ $data[ 'events' ][ $event_id ] = array(
887
+ 'stock' => $global_stock->get_stock_level()
888
+ );
889
+ }
890
+
891
+ wp_localize_script( 'tribe_tickets_frontend_tickets', 'tribe_tickets_stock_data', $data );
892
+ }
893
+
894
  /**
895
  * Returns the array of active modules/providers.
896
  *
src/Tribe/Tickets_Handler.php CHANGED
@@ -51,7 +51,8 @@ class Tribe__Tickets__Tickets_Handler {
51
  $main = Tribe__Tickets__Main::instance();
52
 
53
  foreach ( $main->post_types() as $post_type ) {
54
- add_action( 'save_post_' . $post_type, array( $this, 'save_image_header' ), 10, 2 );
 
55
  }
56
 
57
  add_action( 'wp_ajax_tribe-ticket-email-attendee-list', array( $this, 'ajax_handler_attendee_mail_list' ) );
@@ -71,8 +72,9 @@ class Tribe__Tickets__Tickets_Handler {
71
  */
72
  public function attendees_row_action( $actions ) {
73
  global $post;
 
74
 
75
- if ( in_array( $post->post_type, Tribe__Tickets__Main::instance()->post_types() ) ) {
76
  $url = add_query_arg( array(
77
  'post_type' => $post->post_type,
78
  'page' => self::$attendees_slug,
@@ -226,52 +228,50 @@ class Tribe__Tickets__Tickets_Handler {
226
  *
227
  * @return array
228
  */
229
- private function _generate_filtered_attendees_list( $event_id ) {
 
 
 
 
 
 
230
 
231
  if ( empty( $this->attendees_page ) ) {
232
  $this->attendees_page = 'tribe_events_page_tickets-attendees';
233
  }
234
 
235
- $columns = $this->attendees_table->get_columns();
 
236
  $hidden = get_hidden_columns( $this->attendees_page );
237
 
238
  // We dont want to export html inputs or private data
239
  $hidden[] = 'cb';
240
  $hidden[] = 'provider';
241
 
242
- // Get the data
243
- $items = Tribe__Tickets__Tickets::get_event_attendees( $event_id );
244
-
245
- // if there are attendees, hide any column that the attendee array doesn't contain
246
- if ( count( $items ) ) {
247
- $hidden = array_merge(
248
- $hidden,
249
- array_diff(
250
- array_keys( $columns ),
251
- array_keys( $items[0] )
252
- )
253
- );
254
- }
255
-
256
- // remove the hidden fields from the final list of columns
257
- $hidden = array_filter( $hidden );
258
  $hidden = array_flip( $hidden );
259
  $export_columns = array_diff_key( $columns, $hidden );
260
- $columns_names = array_filter( array_values( $export_columns ) );
261
- $export_columns = array_filter( array_keys( $export_columns ) );
262
 
263
- $rows = array( $columns_names );
264
- //And echo the data
265
- foreach ( $items as $item ) {
 
 
 
 
266
  $row = array();
267
- foreach ( $item as $key => $data ) {
268
- if ( in_array( $key, $export_columns ) ) {
269
- if ( $key == 'check_in' && $data == 1 ) {
270
- $data = esc_html__( 'Yes', 'event-tickets' );
271
- }
272
- $row[ $key ] = $data;
 
 
 
 
273
  }
274
  }
 
275
  $rows[] = array_values( $row );
276
  }
277
 
@@ -279,11 +279,10 @@ class Tribe__Tickets__Tickets_Handler {
279
  }
280
 
281
  /**
282
- * Checks if the user requested a CSV export from the attendees list.
283
- * If so, generates the download and finishes the execution.
284
  */
285
  public function maybe_generate_attendees_csv() {
286
-
287
  if ( empty( $_GET['attendees_csv'] ) || empty( $_GET['attendees_csv_nonce'] ) || empty( $_GET['event_id'] ) ) {
288
  return;
289
  }
@@ -292,8 +291,7 @@ class Tribe__Tickets__Tickets_Handler {
292
  return;
293
  }
294
 
295
-
296
- $items = apply_filters( 'tribe_events_tickets_attendees_csv_items', $this->_generate_filtered_attendees_list( $_GET['event_id'] ) );;
297
  $event = get_post( $_GET['event_id'] );
298
 
299
  if ( ! empty( $items ) ) {
@@ -381,7 +379,7 @@ class Tribe__Tickets__Tickets_Handler {
381
 
382
  $this->attendees_table = new Tribe__Tickets__Attendees_Table();
383
 
384
- $items = $this->_generate_filtered_attendees_list( $_GET['event_id'] );
385
 
386
  $event = get_post( $_GET['event_id'] );
387
 
@@ -447,9 +445,9 @@ class Tribe__Tickets__Tickets_Handler {
447
  /**
448
  * Includes the tickets metabox inside the Event edit screen
449
  *
450
- * @param $post_id
451
  */
452
- public function do_meta_box( $post_id ) {
453
 
454
  $startMinuteOptions = Tribe__View_Helpers::getMinuteOptions( null );
455
  $endMinuteOptions = Tribe__View_Helpers::getMinuteOptions( null );
@@ -458,7 +456,10 @@ class Tribe__Tickets__Tickets_Handler {
458
  $startMeridianOptions = Tribe__View_Helpers::getMeridianOptions( null, true );
459
  $endMeridianOptions = Tribe__View_Helpers::getMeridianOptions( null );
460
 
461
- $tickets = Tribe__Tickets__Tickets::get_event_tickets( $post_id );
 
 
 
462
  include $this->path . 'src/admin-views/meta-box.php';
463
  }
464
 
@@ -504,10 +505,13 @@ class Tribe__Tickets__Tickets_Handler {
504
  /**
505
  * Save or delete the image header for tickets on an event
506
  *
507
- * @param $post_id
508
- * @param $post
509
  */
510
- public function save_image_header( $post_id, $post ) {
 
 
 
 
511
  // don't do anything on autosave or auto-draft either or massupdates
512
  if ( wp_is_post_autosave( $post_id ) || wp_is_post_revision( $post_id ) ) {
513
  return;
@@ -522,6 +526,29 @@ class Tribe__Tickets__Tickets_Handler {
522
  return;
523
  }
524
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
525
  /**
526
  * Static Singleton Factory Method
527
  *
51
  $main = Tribe__Tickets__Main::instance();
52
 
53
  foreach ( $main->post_types() as $post_type ) {
54
+ add_action( 'save_post_' . $post_type, array( $this, 'save_image_header' ) );
55
+ add_action( 'save_post_' . $post_type, array( $this, 'save_global_stock' ) );
56
  }
57
 
58
  add_action( 'wp_ajax_tribe-ticket-email-attendee-list', array( $this, 'ajax_handler_attendee_mail_list' ) );
72
  */
73
  public function attendees_row_action( $actions ) {
74
  global $post;
75
+ $tickets = Tribe__Tickets__Tickets::get_event_tickets( $post->ID );
76
 
77
+ if ( in_array( $post->post_type, Tribe__Tickets__Main::instance()->post_types() ) && ! empty( $tickets ) ) {
78
  $url = add_query_arg( array(
79
  'post_type' => $post->post_type,
80
  'page' => self::$attendees_slug,
228
  *
229
  * @return array
230
  */
231
+ private function generate_filtered_attendees_list( $event_id ) {
232
+ /**
233
+ * Fire immediately prior to the generation of a filtered (exportable) attendee list.
234
+ *
235
+ * @param int $event_id
236
+ */
237
+ do_action( 'tribe_events_tickets_generate_filtered_attendees_list', $event_id );
238
 
239
  if ( empty( $this->attendees_page ) ) {
240
  $this->attendees_page = 'tribe_events_page_tickets-attendees';
241
  }
242
 
243
+ $items = Tribe__Tickets__Tickets::get_event_attendees( $event_id );
244
+ $columns = get_column_headers( get_current_screen() );
245
  $hidden = get_hidden_columns( $this->attendees_page );
246
 
247
  // We dont want to export html inputs or private data
248
  $hidden[] = 'cb';
249
  $hidden[] = 'provider';
250
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
251
  $hidden = array_flip( $hidden );
252
  $export_columns = array_diff_key( $columns, $hidden );
 
 
253
 
254
+ // Add the export column headers as the first row
255
+ $rows = array(
256
+ array_values( $export_columns ),
257
+ );
258
+
259
+ foreach ( $items as $single_item ) {
260
+ // Fresh row!
261
  $row = array();
262
+
263
+ foreach ( $export_columns as $column_id => $column_name ) {
264
+ // If additional columns have been added to the attendee list table we can obtain the
265
+ // values by calling the table object's column_default() method - any other values
266
+ // should simply be passed back unmodified
267
+ $row[ $column_id ] = $this->attendees_table->column_default( $single_item, $column_id );
268
+
269
+ // Special handling for the check_in column
270
+ if ( 'check_in' === $column_id && 1 == $single_item[ $column_id ] ) {
271
+ $row[ $column_id ] = esc_html__( 'Yes', 'event-tickets' );
272
  }
273
  }
274
+
275
  $rows[] = array_values( $row );
276
  }
277
 
279
  }
280
 
281
  /**
282
+ * Checks if the user requested a CSV export from the attendees list.
283
+ * If so, generates the download and finishes the execution.
284
  */
285
  public function maybe_generate_attendees_csv() {
 
286
  if ( empty( $_GET['attendees_csv'] ) || empty( $_GET['attendees_csv_nonce'] ) || empty( $_GET['event_id'] ) ) {
287
  return;
288
  }
291
  return;
292
  }
293
 
294
+ $items = apply_filters( 'tribe_events_tickets_attendees_csv_items', $this->generate_filtered_attendees_list( $_GET['event_id'] ) );;
 
295
  $event = get_post( $_GET['event_id'] );
296
 
297
  if ( ! empty( $items ) ) {
379
 
380
  $this->attendees_table = new Tribe__Tickets__Attendees_Table();
381
 
382
+ $items = $this->generate_filtered_attendees_list( $_GET['event_id'] );
383
 
384
  $event = get_post( $_GET['event_id'] );
385
 
445
  /**
446
  * Includes the tickets metabox inside the Event edit screen
447
  *
448
+ * @param WP_Post $post
449
  */
450
+ public function do_meta_box( $post ) {
451
 
452
  $startMinuteOptions = Tribe__View_Helpers::getMinuteOptions( null );
453
  $endMinuteOptions = Tribe__View_Helpers::getMinuteOptions( null );
456
  $startMeridianOptions = Tribe__View_Helpers::getMeridianOptions( null, true );
457
  $endMeridianOptions = Tribe__View_Helpers::getMeridianOptions( null );
458
 
459
+ $show_global_stock = Tribe__Tickets__Tickets::global_stock_available();
460
+ $tickets = Tribe__Tickets__Tickets::get_event_tickets( $post->ID );
461
+ $global_stock = new Tribe__Tickets__Global_Stock( $post->ID );
462
+
463
  include $this->path . 'src/admin-views/meta-box.php';
464
  }
465
 
505
  /**
506
  * Save or delete the image header for tickets on an event
507
  *
508
+ * @param int $post_id
 
509
  */
510
+ public function save_image_header( $post_id ) {
511
+ if ( ! ( isset($_POST[ 'tribe-tickets-post-settings' ]) && wp_verify_nonce( $_POST[ 'tribe-tickets-post-settings' ], 'tribe-tickets-meta-box' ) ) ) {
512
+ return;
513
+ }
514
+
515
  // don't do anything on autosave or auto-draft either or massupdates
516
  if ( wp_is_post_autosave( $post_id ) || wp_is_post_revision( $post_id ) ) {
517
  return;
526
  return;
527
  }
528
 
529
+ /**
530
+ * Save the current global stock properties for this event.
531
+ *
532
+ * @param int $post_id
533
+ */
534
+ public function save_global_stock( $post_id ) {
535
+ if ( ! ( isset( $_POST[ 'tribe-tickets-post-settings' ] ) && wp_verify_nonce( $_POST[ 'tribe-tickets-post-settings' ], 'tribe-tickets-meta-box' ) ) ) {
536
+ return;
537
+ }
538
+
539
+ // Bail on autosaves/bulk updates
540
+ if ( wp_is_post_autosave( $post_id ) || wp_is_post_revision( $post_id ) ) {
541
+ return;
542
+ }
543
+
544
+ $enable = ! empty( $_POST[ 'tribe-tickets-enable-global-stock' ] );
545
+ $stock = (int) @$_POST[ 'tribe-tickets-global-stock' ];
546
+
547
+ $post_global_stock = new Tribe__Tickets__Global_Stock( $post_id );
548
+ $post_global_stock->enable( $enable );
549
+ $post_global_stock->set_stock_level( $stock );
550
+ }
551
+
552
  /**
553
  * Static Singleton Factory Method
554
  *
src/admin-views/attendees.php CHANGED
@@ -15,8 +15,9 @@ foreach ( $tickets as $ticket ) {
15
  $total_sold += $ticket->qty_sold() + $ticket->qty_pending();
16
  $total_pending += $ticket->qty_pending();
17
  }
18
- $total_completed = $total_sold - $total_pending;
19
-
 
20
  ?>
21
 
22
  <div class="wrap tribe-attendees-page">
@@ -91,6 +92,13 @@ $total_completed = $total_sold - $total_pending;
91
  <strong><?php esc_html_e( 'Checked in:', 'event-tickets' ); ?></strong>
92
  <span id="total_checkedin"><?php echo esc_html( $checkedin ); ?></span>
93
  </li>
 
 
 
 
 
 
 
94
  </ul>
95
  <?php do_action( 'tribe_events_tickets_attendees_totals_bottom', $event_id ); ?>
96
  </div>
15
  $total_sold += $ticket->qty_sold() + $ticket->qty_pending();
16
  $total_pending += $ticket->qty_pending();
17
  }
18
+ $total_completed = $total_sold - $total_pending;
19
+ $total_attendees = Tribe__Tickets__Tickets::get_event_attendees_count( $event_id );
20
+ $deleted_attendees = $total_sold - $total_attendees;
21
  ?>
22
 
23
  <div class="wrap tribe-attendees-page">
92
  <strong><?php esc_html_e( 'Checked in:', 'event-tickets' ); ?></strong>
93
  <span id="total_checkedin"><?php echo esc_html( $checkedin ); ?></span>
94
  </li>
95
+
96
+ <?php if ( $deleted_attendees > 0 ): ?>
97
+ <li>
98
+ <strong><?php esc_html_e( 'Deleted:', 'event-tickets' ); ?></strong>
99
+ <span id="total_deleted"><?php echo esc_html( $deleted_attendees ); ?></span>
100
+ </li>
101
+ <?php endif; ?>
102
  </ul>
103
  <?php do_action( 'tribe_events_tickets_attendees_totals_bottom', $event_id ); ?>
104
  </div>
src/admin-views/list.php CHANGED
@@ -23,6 +23,9 @@
23
  $modules = Tribe__Tickets__Tickets::modules();
24
 
25
  foreach ( $tickets as $ticket ) {
 
 
 
26
  $controls = array();
27
  $provider = $ticket->provider_class;
28
  $provider_obj = call_user_func( array( $provider, 'get_instance' ) );
@@ -75,9 +78,17 @@
75
  <?php endif; ?>
76
  <tr>
77
  <td>
78
- <p class="ticket_name"><?php
79
- printf( "<a href='#' attr-provider='%s' attr-ticket-id='%s' class='ticket_edit'>%s</a></span>", esc_attr( $ticket->provider_class ), esc_attr( $ticket->ID ), esc_html( $ticket->name ) );
80
- ?></p>
 
 
 
 
 
 
 
 
81
 
82
  <div class="ticket_controls">
83
  <?php echo join( ' | ', $controls ); ?>
@@ -90,27 +101,7 @@
90
  </td>
91
 
92
  <td nowrap="nowrap">
93
- <?php
94
- $stock = $ticket->original_stock();
95
- $sold = $ticket->qty_sold();
96
- $cancelled = $ticket->qty_cancelled();
97
-
98
- if ( empty( $stock ) && $stock !== 0 ) : ?>
99
- <?php echo sprintf( esc_html__( 'Sold %d', 'event-tickets' ), esc_html( $sold ) ); ?>
100
- <?php else : ?>
101
- <?php
102
- $cancelled_entry = empty( $cancelled ) ? '' : esc_html(sprintf(
103
- __( ' (%1$d %2$s)' ), $cancelled,
104
- _n( 'unit cancelled', 'units cancelled', $cancelled, 'event-tickets' )
105
- ));
106
- $line = sprintf(
107
- esc_html__( 'Sold %1$d of %2$d%3$s', 'event-tickets' ), esc_html( $sold ),
108
- esc_html( $stock ), $cancelled_entry
109
- );
110
-
111
- echo $line;
112
- ?>
113
- <?php endif; ?>
114
  </td>
115
  <td width="40%" valign="top">
116
  <?php echo esc_html( $ticket->description ); ?>
23
  $modules = Tribe__Tickets__Tickets::modules();
24
 
25
  foreach ( $tickets as $ticket ) {
26
+ /**
27
+ * @var Tribe__Tickets__Ticket_Object $ticket
28
+ */
29
  $controls = array();
30
  $provider = $ticket->provider_class;
31
  $provider_obj = call_user_func( array( $provider, 'get_instance' ) );
78
  <?php endif; ?>
79
  <tr>
80
  <td>
81
+ <p class="ticket_name">
82
+ <?php
83
+ printf(
84
+ "<a href='#' attr-provider='%s' attr-ticket-id='%s' class='ticket_edit'>%s</a>",
85
+ esc_attr( $ticket->provider_class ),
86
+ esc_attr( $ticket->ID ),
87
+ esc_html( $ticket->name )
88
+ );
89
+ do_action( 'event_tickets_ticket_list_after_ticket_name', $ticket );
90
+ ?>
91
+ </p>
92
 
93
  <div class="ticket_controls">
94
  <?php echo join( ' | ', $controls ); ?>
101
  </td>
102
 
103
  <td nowrap="nowrap">
104
+ <?php echo tribe_tickets_get_ticket_stock_message( $ticket ); ?>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
105
  </td>
106
  <td width="40%" valign="top">
107
  <?php echo esc_html( $ticket->description ); ?>
src/admin-views/meta-box.php CHANGED
@@ -1,4 +1,10 @@
1
  <?php
 
 
 
 
 
 
2
  // Don't load directly
3
  if ( ! defined( 'ABSPATH' ) ) {
4
  die( '-1' );
@@ -16,6 +22,8 @@ $modules = Tribe__Tickets__Tickets::modules();
16
 
17
  <table id="event_tickets" class="eventtable">
18
  <?php
 
 
19
  if ( get_post_meta( get_the_ID(), '_EventOrigin', true ) === 'community-events' ) {
20
  ?>
21
  <tr>
@@ -26,7 +34,7 @@ $modules = Tribe__Tickets__Tickets::modules();
26
  <?php
27
  }
28
  ?>
29
- <tr>
30
  <td colspan="2" class="tribe_sectionheader updated">
31
  <table class="eventtable ticket_list eventForm">
32
  <tr class="tribe-tickets-image-upload">
@@ -43,7 +51,7 @@ $modules = Tribe__Tickets__Tickets::modules();
43
  <div class="tribe_preview" id="tribe_ticket_header_preview">
44
  <?php echo $header_img; ?>
45
  </div>
46
- <p class="description"><a href="#" id="tribe_ticket_header_remove"><?php esc_html_e( 'Remove' ); ?></a></p>
47
 
48
  <input type="hidden" id="tribe_ticket_header_image_id" name="tribe_ticket_header_image_id" value="<?php echo esc_attr( $header_id ); ?>" />
49
  </td>
@@ -51,6 +59,46 @@ $modules = Tribe__Tickets__Tickets::modules();
51
  </table>
52
  </td>
53
  </tr>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
54
  <tr>
55
  <td colspan="2" class="tribe_sectionheader ticket_list_container">
56
 
@@ -160,7 +208,7 @@ $modules = Tribe__Tickets__Tickets::modules();
160
  <?php esc_html_e( 'When will ticket sales occur?', 'event-tickets' ); ?>
161
  <?php
162
  // Why break in and out of PHP? because I want the space between the phrases without including them in the translations
163
- if ( class_exists( 'Tribe__Events__Main' ) && Tribe__Events__Main::POSTTYPE === get_post_type( $post_id ) ) {
164
  esc_html_e( "If you don't set a start/end date for sales, tickets will be available from now until the event ends.", 'event-tickets' );
165
  }
166
  ?>
@@ -168,7 +216,14 @@ $modules = Tribe__Tickets__Tickets::modules();
168
  </td>
169
  </tr>
170
 
171
- <?php do_action( 'tribe_events_tickets_metabox_advanced', get_the_ID(), null ); ?>
 
 
 
 
 
 
 
172
 
173
  <tr class="ticket bottom">
174
  <td></td>
1
  <?php
2
+ /**
3
+ * @var WP_Post $post
4
+ * @var bool $show_global_stock
5
+ * @var Tribe__Tickets__Global_Stock $global_stock
6
+ */
7
+
8
  // Don't load directly
9
  if ( ! defined( 'ABSPATH' ) ) {
10
  die( '-1' );
22
 
23
  <table id="event_tickets" class="eventtable">
24
  <?php
25
+ wp_nonce_field( 'tribe-tickets-meta-box', 'tribe-tickets-post-settings' );
26
+
27
  if ( get_post_meta( get_the_ID(), '_EventOrigin', true ) === 'community-events' ) {
28
  ?>
29
  <tr>
34
  <?php
35
  }
36
  ?>
37
+ <tr class="event-wide-settings">
38
  <td colspan="2" class="tribe_sectionheader updated">
39
  <table class="eventtable ticket_list eventForm">
40
  <tr class="tribe-tickets-image-upload">
51
  <div class="tribe_preview" id="tribe_ticket_header_preview">
52
  <?php echo $header_img; ?>
53
  </div>
54
+ <p class="description"><a href="#" id="tribe_ticket_header_remove"><?php esc_html_e( 'Remove', 'event-tickets' ); ?></a></p>
55
 
56
  <input type="hidden" id="tribe_ticket_header_image_id" name="tribe_ticket_header_image_id" value="<?php echo esc_attr( $header_id ); ?>" />
57
  </td>
59
  </table>
60
  </td>
61
  </tr>
62
+ <?php if ( $show_global_stock ): ?>
63
+ <tr id="tribe-global-stock-settings" class="event-wide-settings">
64
+ <td colspan="2">
65
+ <table class="eventtable ticket_list eventForm">
66
+ <tr>
67
+ <td>
68
+ <label for="tribe-tickets-enable-global-stock">
69
+ <?php esc_html_e( 'Enable global stock', 'event-tickets' ); ?>
70
+ </label>
71
+ </td>
72
+ <td>
73
+ <input type="checkbox" name="tribe-tickets-enable-global-stock" id="tribe-tickets-enable-global-stock" value="1" <?php checked( $global_stock->is_enabled() ); ?> />
74
+ </td>
75
+ </tr>
76
+ <tr id="tribe-tickets-global-stock-level">
77
+ <td>
78
+ <label for="tribe-tickets-global-stock">
79
+ <?php esc_html_e( 'Global stock level', 'event-tickets' ); ?>
80
+ </label>
81
+ </td>
82
+ <td>
83
+ <input type="number" name="tribe-tickets-global-stock" id="tribe-tickets-global-stock" value="<?php echo esc_attr( $global_stock->get_stock_level() ); ?>" />
84
+ <span class="tribe-tickets-global-sales">
85
+ <?php echo esc_html( sprintf( _n( '(%s sold)', '(%s sold)', $global_stock->tickets_sold(), 'event-tickets' ), $global_stock->tickets_sold() ) ); ?>
86
+ </span>
87
+ </td>
88
+ </tr>
89
+ </table>
90
+ </td>
91
+ </tr>
92
+ <?php endif; ?>
93
+
94
+ <?php
95
+ /**
96
+ * Fired to allow for the insertion of additional content into the ticket admin form before the tickets listing
97
+ *
98
+ * @param Post ID
99
+ */
100
+ do_action( 'tribe_events_tickets_metabox_pre', get_the_ID() ); ?>
101
+
102
  <tr>
103
  <td colspan="2" class="tribe_sectionheader ticket_list_container">
104
 
208
  <?php esc_html_e( 'When will ticket sales occur?', 'event-tickets' ); ?>
209
  <?php
210
  // Why break in and out of PHP? because I want the space between the phrases without including them in the translations
211
+ if ( class_exists( 'Tribe__Events__Main' ) && Tribe__Events__Main::POSTTYPE === get_post_type( $post ) ) {
212
  esc_html_e( "If you don't set a start/end date for sales, tickets will be available from now until the event ends.", 'event-tickets' );
213
  }
214
  ?>
216
  </td>
217
  </tr>
218
 
219
+ <?php
220
+ /**
221
+ * Fired to allow for the insertion of additional content into the ticket admin form
222
+ *
223
+ * @var Post ID
224
+ * @var null Ticket ID
225
+ */
226
+ do_action( 'tribe_events_tickets_metabox_advanced', get_the_ID(), null ); ?>
227
 
228
  <tr class="ticket bottom">
229
  <td></td>
src/admin-views/rsvp-metabox-advanced.php CHANGED
@@ -1,5 +1,5 @@
1
  <tr class="<?php $this->tr_class(); ?>">
2
- <td><label for="ticket_woo_stock"><?php esc_html_e( 'Stock:', 'event-tickets' ); ?></label></td>
3
  <td>
4
  <input type='text' id='ticket_rsvp_stock' name='ticket_rsvp_stock' class="ticket_field" size='7' value='<?php echo esc_attr( $stock ); ?>'/>
5
 
1
  <tr class="<?php $this->tr_class(); ?>">
2
+ <td><label for="ticket_rsvp_stock"><?php esc_html_e( 'Stock:', 'event-tickets' ); ?></label></td>
3
  <td>
4
  <input type='text' id='ticket_rsvp_stock' name='ticket_rsvp_stock' class="ticket_field" size='7' value='<?php echo esc_attr( $stock ); ?>'/>
5
 
src/deprecated/TribeEventsTicketObject.php CHANGED
File without changes
src/deprecated/TribeEventsTickets.php CHANGED
File without changes
src/deprecated/TribeEventsTicketsAttendeesTable.php CHANGED
File without changes
src/deprecated/TribeEventsTicketsMetabox.php CHANGED
File without changes
src/deprecated/TribeEventsTicketsPro.php CHANGED
File without changes
src/deprecated/Tribe__Events__Tickets__Attendees_Table.php CHANGED
File without changes
src/deprecated/Tribe__Events__Tickets__Metabox.php CHANGED
File without changes
src/deprecated/Tribe__Events__Tickets__Ticket_Object.php CHANGED
File without changes
src/deprecated/Tribe__Events__Tickets__Tickets.php CHANGED
File without changes
src/deprecated/Tribe__Events__Tickets__Tickets_Pro.php CHANGED
File without changes
src/resources/css/rsvp.css CHANGED
@@ -23,12 +23,16 @@ table.tribe-events-tickets td {
23
  border: 0;
24
  }
25
 
 
 
 
 
26
  .tribe-rsvp-messages {
 
27
  padding: 10px 10px 5px;
28
  }
29
 
30
  .tribe-rsvp-message {
31
- margin-bottom: 5px;
32
  -khtml-border-radius: 3px;
33
  -moz-border-radius: 3px;
34
  -webkit-border-radius: 3px;
@@ -54,4 +58,4 @@ table.tribe-events-tickets td {
54
 
55
  .tribe-ticket-quantity {
56
  width: 100%;
57
- }
23
  border: 0;
24
  }
25
 
26
+ .tribe-rsvp-message-display .tribe-rsvp-messages {
27
+ display: block;
28
+ }
29
+
30
  .tribe-rsvp-messages {
31
+ display: none;
32
  padding: 10px 10px 5px;
33
  }
34
 
35
  .tribe-rsvp-message {
 
36
  -khtml-border-radius: 3px;
37
  -moz-border-radius: 3px;
38
  -webkit-border-radius: 3px;
58
 
59
  .tribe-ticket-quantity {
60
  width: 100%;
61
+ }
src/resources/css/rsvp.min.css ADDED
@@ -0,0 +1 @@
 
1
+ .tribe-tickets-meta-row{display:none}.tribe-tickets-has-rsvp .tribe-tickets-meta-row{display:table-row}.tribe-tickets-attendee{padding:10px}table.tribe-events-tickets td{padding:8px 10px}.tribe-events-style-full .tribe-events-tickets .tribe-tickets-attendee table,.tribe-events-style-full .tribe-events-tickets .tribe-tickets-attendee td,.tribe-events-style-full .tribe-events-tickets .tribe-tickets-attendee tr,.tribe-events-tickets .tribe-tickets-attendee table,.tribe-events-tickets .tribe-tickets-attendee td,.tribe-events-tickets .tribe-tickets-attendee tr{border:0}.tribe-rsvp-message-display .tribe-rsvp-messages{display:block}.tribe-rsvp-messages{display:none;padding:10px 10px 5px}.tribe-rsvp-message{border-radius:3px;border-style:solid;border-width:1px;font-family:Helvetica,Arial,sans;font-size:12px;margin:0 0 5px;padding:0 .6em}.tribe-rsvp-message-success{background-color:#ffffe0;border-color:#e6db55}.tribe-rsvp-message-error{background-color:#ffebe8;border-color:#c00}.tribe-ticket-quantity{width:100%}
src/resources/css/tickets-attendees-print.min.css CHANGED
@@ -1 +1 @@
1
- @media print{#adminmenuback,#adminmenuwrap,#icon-edit,#show-settings-link,#total_checkedin_wrapper,#wpfooter,.check-column,.column-check_in,.screen-meta-toggle,.tablenav,h2{display:none}.wrap h2+h2{display:block}#wpcontent,#wpfooter{margin-left:20px}#the-list tr{background:#fff;border:0;border-bottom:1px solid #333}a.row-title{color:#000}.widefat tfoot tr th,.widefat thead tr th{background:0 0;text-shadow:none}.postbox h3{text-shadow:none}#total_tickets_sold{display:block}.ticket_list h4{font-size:13px}.ticket_list tr td{font-size:11px;line-height:15px}.ticket_list tr td .totals{width:75%;height:100px;padding-top:40px;line-height:30px}table{page-break-inside:auto}tr{page-break-inside:avoid;page-break-after:auto}thead{display:table-header-group}tfoot{display:table-footer-group}}
1
+ @media print{#adminmenuback,#adminmenuwrap,#icon-edit,#show-settings-link,#total_checkedin_wrapper,#wpfooter,.check-column,.column-check_in,.screen-meta-toggle,.tablenav,h2{display:none}.wrap h2+h2{display:block}#wpcontent,#wpfooter{margin-left:20px}#the-list tr{background:#fff;border:0;border-bottom:1px solid #333}a.row-title{color:#000}.widefat tfoot tr th,.widefat thead tr th{background:none;text-shadow:none}.postbox h3{text-shadow:none}#total_tickets_sold{display:block}.ticket_list h4{font-size:13px}.ticket_list tr td{font-size:11px;line-height:15px}.ticket_list tr td .totals{width:75%;height:100px;padding-top:40px;line-height:30px}table{page-break-inside:auto}tr{page-break-inside:avoid;page-break-after:auto}thead{display:table-header-group}tfoot{display:table-footer-group}}
src/resources/css/tickets-attendees.min.css CHANGED
@@ -1 +1 @@
1
- .icon32-tickets-attendees{background-image:url(../images/attendees-screen-icon.png)!important}.tickets_checked td{opacity:.7;text-decoration:line-through}.tickets_checked a.tickets_checkin{display:none}.tickets_checked td.column-check_in{text-decoration:none!important}a.tickets_uncheckin{display:none}.tickets_checked a.tickets_uncheckin{display:block}.column-attendee_id,.column-order_id{width:70px}.ticket_list h4{font-size:16px}.ticket_list tr td{font-size:15px;line-height:24px}.ticket_list tr td .totals{background:#ACA000;color:#fff;font-weight:700;font-size:20px;text-align:center;width:75%;height:100px;padding-bottom:20px;padding-top:20px;line-height:30px}.ticket_list tr td .totals #sales_breakdown_wrapper{font-size:14px;line-height:1}.ticket_list tr td .totals #sales_breakdown_wrapper #total_issued{margin-right:14px}.wp-pointer-arrow{right:50px!important;left:auto!important}a.export,input.email,input.print{margin:1px 8px 0 0!important}#attendees_email_wrapper{padding:20px 0;overflow:hidden}#attendees_email_wrapper #email_response{text-align:center;padding:80px 0 30px;background:url(../images/tribe-loading.gif) 50% 30%/32px 32px no-repeat;border:0;display:none}#attendees_email_wrapper #email_errors{text-align:center;padding:10px 0;border:0;display:none}#attendees_email_wrapper label{display:block}#attendees_email_wrapper label span{display:inline-block;width:105px;padding-left:15px}#attendees_email_wrapper select{margin-top:10px}#attendees_email_wrapper input[type=text],#attendees_email_wrapper select{width:260px;border-radius:0;background:#fff;border-color:#ccc;line-height:30px;height:30px}.attendees_email_dialog{padding:20px;border-radius:0;background:#F7F7F7}.attendees_email_dialog .ui-dialog-titlebar,.attendees_email_dialog .ui-widget-header{background:#eee;border-color:#ccc;border-radius:0;height:30px;line-height:30px}.attendees_email_dialog .attendees_or{display:block;font-weight:700;margin:15px 0 15px 125px;width:260px;text-align:center;text-transform:uppercase}.attendees_email_dialog .ui-dialog-title{margin-top:-2px}.attendees_email_dialog .ui-dialog-buttonpane{background:0 0;border-color:transparent;margin-top:6px;padding-top:0;margin-left:23px}.attendees_email_dialog .ui-state-default,.attendees_email_dialog .ui-widget-content .ui-state-default,.attendees_email_dialog .ui-widget-header .ui-state-default{background:#666;border:none;border-radius:0;color:#FFF;font-size:13px;letter-spacing:0;padding:6px 10px}
1
+ #tribe-attendees-summary{padding-bottom:10px;position:relative}#tribe-attendees-summary .welcome-panel-column-container{position:initial}#tribe-attendees-summary .about-description,#tribe-attendees-summary h3{width:66%}#tribe-attendees-summary .about-description a{color:inherit}#tribe-attendees-summary .welcome-panel-column li{line-height:1.4em;margin-bottom:2px}#tribe-attendees-summary .welcome-panel-last{position:absolute;top:0;bottom:0;right:0;border-left:1px solid #eee;padding:10px 20px 5px;width:200px}#tribe-attendees-summary .welcome-panel-last h4{font-size:1.5em;text-align:center}#tribe-attendees-summary .welcome-panel-last li{font-size:1.2em}@media screen and (max-width:870px){#tribe-attendees-summary .about-description,#tribe-attendees-summary h3{width:100%}#tribe-attendees-summary .welcome-panel-column li{display:list-item}#tribe-attendees-summary .welcome-panel-column-container{margin-bottom:115px}#tribe-attendees-summary .welcome-panel-last{top:initial;bottom:0;left:0;right:0;border-top:1px solid #eee;border-left:0;width:100%}#tribe-attendees-summary .welcome-panel-last ul{float:left;width:50%}}@media screen and (min-width:870px){#tribe-attendees-summary .welcome-panel-last ul:first-child{margin-bottom:0}#tribe-attendees-summary .welcome-panel-last ul:last-child{margin-top:0}}.tribe-attendees-page .tickets_checked td{opacity:.7;text-decoration:line-through}.tribe-attendees-page .tickets_checked a.tickets_checkin{display:none}.tribe-attendees-page .tickets_checked td.column-check_in{text-decoration:none!important}.tribe-attendees-page a.tickets_uncheckin{display:none}.tribe-attendees-page .tickets_checked a.tickets_uncheckin{display:block}.tribe-attendees-page .column-attendee_id,.tribe-attendees-page .column-order_id{width:70px}.tribe-attendees-page .email,.tribe-attendees-page .export,.tribe-attendees-page .print{margin:1px 8px 0 0}@media screen and (max-width:782px){.tribe-attendees-email-message .notice-dismiss{padding:9px}}.tribe-attendees-email .button-primary{margin-top:3px}.tribe-attendees-email .tribe-attendees-email-message{float:left;margin:0}.tribe-attendees-email-message ul{margin:.5em 0;padding:2px;font-size:13px;line-height:1.5}.tribe-attendees-email-message ul li:last-child{margin-bottom:0}#attendees_email_wrapper{margin-top:10px;padding:20px;overflow:hidden}#attendees_email_wrapper label{display:block}#attendees_email_wrapper label span{display:inline-block;width:105px}#attendees_email_wrapper .attendees_or{text-align:center;display:block;font-size:20px;margin:20px 0}#attendees_email_wrapper input[type=text],#attendees_email_wrapper select{width:285px}
src/resources/css/tickets.css CHANGED
@@ -47,6 +47,10 @@ p.ticket_name {
47
  display : inline;
48
  }
49
 
 
 
 
 
50
  .ticket_form h4.ticket_form_title_edit {
51
  display : none;
52
  }
@@ -66,3 +70,19 @@ p.ticket_name {
66
  .tribe-tickets-remaining {
67
  font-size: 10px;
68
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
47
  display : inline;
48
  }
49
 
50
+ .ticket_form p.description.detailed-description {
51
+ display : block;
52
+ }
53
+
54
  .ticket_form h4.ticket_form_title_edit {
55
  display : none;
56
  }
70
  .tribe-tickets-remaining {
71
  font-size: 10px;
72
  }
73
+
74
+ .eventtable {
75
+ margin : 0;
76
+ padding-top : 0;
77
+ width : 100%;
78
+ }
79
+
80
+ #event_tickets, .eventtable.ticket_list.eventForm {
81
+ table-layout : fixed;
82
+ }
83
+
84
+ /* Specific goal of this rule is to prevent the number input busting the ticket form layout under Twenty Fifteen */
85
+ .tribe-theme-twentyfifteen .tribe-events-tickets input[type="number"] {
86
+ padding: 0.375em;
87
+ width: 100%;
88
+ }
src/resources/css/tickets.min.css CHANGED
@@ -1 +1 @@
1
- #ticket_form{display:none}#ticket_form input[type=radio]{margin-right:5px}#ticket_form span{margin-right:10px}.ticket_list tr td{padding:4px 7px 2px;vertical-align:top}.ticket_list tr td div.ticket_controls{visibility:hidden}.ticket_list tr:hover td div.ticket_controls{visibility:visible}.ticket_list h4{text-transform:uppercase;border-bottom:1px solid #e5e5e5;padding-bottom:6px}.ticket_list h4 a{font-weight:400;font-size:11px;text-transform:none}p.ticket_name{font-size:13px;font-weight:700}.ticket_form p.description{display:inline}.ticket_form h4.ticket_form_title_edit{display:none}#tribe_ticket_header_preview img{max-width:95%!important;height:auto!important}#tribe_ticket_header_remove,.ticket_time{display:none}.tribe-tickets-remaining {font-size: 10px;}
1
+ #ticket_form{display:none}#ticket_form input[type=radio]{margin-right:5px}#ticket_form span{margin-right:10px}.ticket_list tr td{padding:4px 7px 2px;vertical-align:top}.ticket_list tr td div.ticket_controls{visibility:hidden}.ticket_list tr:hover td div.ticket_controls{visibility:visible}.ticket_list h4{text-transform:uppercase;border-bottom:1px solid #e5e5e5;padding-bottom:6px}.ticket_list h4 a{font-weight:400;font-size:11px;text-transform:none}p.ticket_name{font-size:13px;font-weight:700}.ticket_form p.description{display:inline}.ticket_form p.description.detailed-description{display:block}.ticket_form h4.ticket_form_title_edit{display:none}#tribe_ticket_header_preview img{max-width:95%!important;height:auto!important}#tribe_ticket_header_remove,.ticket_time{display:none}.tribe-tickets-remaining{font-size:10px}.eventtable{margin:0;padding-top:0;width:100%}#event_tickets,.eventtable.ticket_list.eventForm{table-layout:fixed}.tribe-theme-twentyfifteen .tribe-events-tickets input[type=number]{padding:.375em;width:100%}
src/resources/js/frontend-ticket-form.js ADDED
@@ -0,0 +1,290 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ var tribe_tickets_ticket_form = {};
2
+
3
+ /**
4
+ * Provides global stock handling for frontend ticket forms.
5
+ *
6
+ * @var object tribe_tickets_stock_data
7
+ */
8
+ ( function( $, my ) {
9
+ var $tickets_lists;
10
+ var $quantity_fields;
11
+
12
+ my.init = function() {
13
+ $tickets_lists = $( '.tribe-events-tickets' );
14
+ $quantity_fields = $tickets_lists.find( '.quantity' ).find( 'input' );
15
+ $quantity_fields.on( 'change', my.on_quantity_change );
16
+ };
17
+
18
+ /**
19
+ * Every time a ticket quantity field is changed we should evaluate
20
+ * the new quantity and ensure it is still "in bounds" with relation
21
+ * to global stock.
22
+ */
23
+ my.on_quantity_change = function() {
24
+ var $this = $( this );
25
+ var ticket_id = my.get_matching_ticket_id( this );
26
+
27
+ if ( my.ticket_uses_global_stock( ticket_id ) ) {
28
+ my.global_stock_quantity_changed( $this, ticket_id );
29
+ } else {
30
+ my.normal_stock_quantity_changed( $this, ticket_id );
31
+ }
32
+ };
33
+
34
+ /**
35
+ * Handle updates and checks where the modified quantity selector belongs to
36
+ * a ticket that uses global stock.
37
+ *
38
+ * @param $input
39
+ * @param ticket_id
40
+ */
41
+ my.global_stock_quantity_changed = function( $input, ticket_id ) {
42
+ var new_quantity = $input.val();
43
+ var event_id = my.get_event_id( ticket_id );
44
+ var ticket_cap = my.get_cap( ticket_id );
45
+ var event_stock = my.get_global_stock( event_id );
46
+ var total_requested = my.currently_requested_global_event_stock( event_id );
47
+
48
+ // If the total stock requested across all inputs now exceeds what's available, adjust this one
49
+ if ( total_requested > event_stock ) {
50
+ new_quantity -= total_requested - event_stock;
51
+ }
52
+
53
+ // If sales for this input have been capped, adjust if necessary to stay within the cap
54
+ if ( my.stock_mode_is_capped( ticket_id ) && new_quantity > ticket_cap ) {
55
+ new_quantity = ticket_cap;
56
+ }
57
+
58
+ // Do not let our adjustments take the new quantity below zero, however
59
+ if ( 0 >= new_quantity ) {
60
+ new_quantity = 0;
61
+ }
62
+
63
+ $input.val( new_quantity );
64
+ my.update_available_stock_counts( event_id );
65
+ };
66
+
67
+ /**
68
+ * Handle updates and checks where the modified quantity selector belongs to
69
+ * a ticket that does not use global stock.
70
+ *
71
+ * @param $input
72
+ * @param ticket_id
73
+ */
74
+ my.normal_stock_quantity_changed = function( $input, ticket_id ) {
75
+ var new_quantity = $input.val();
76
+ var available_stock = my.get_single_stock( ticket_id );
77
+ var remaining;
78
+
79
+ // Keep in check (should be handled for us by numeric inputs in most browsers, but let's be safe)
80
+ if ( new_quantity > available_stock ) {
81
+ new_quantity = available_stock;
82
+ }
83
+
84
+ // Update
85
+ $input.val( new_quantity );
86
+ remaining = available_stock - new_quantity;
87
+ $tickets_lists.find( '.available-stock[data-product-id=' + ticket_id + ']').html( remaining );
88
+ };
89
+
90
+ /**
91
+ * Each ticket list typically shows the remaining inventory next to each
92
+ * quantity input. This method updates those counts appropriately.
93
+ *
94
+ * @param event_id
95
+ */
96
+ my.update_available_stock_counts = function( event_id ) {
97
+ var tickets = my.get_tickets_of( event_id );
98
+ var remaining = my.get_global_stock( event_id ) - my.currently_requested_global_event_stock( event_id );
99
+
100
+ for ( var ticket_id in tickets ) {
101
+ if ( ! tickets.hasOwnProperty( ticket_id ) ) {
102
+ continue;
103
+ }
104
+
105
+ // Do not allow a sub-zero tickets remaining count
106
+ if ( remaining < 0 ) {
107
+ remaining = 0;
108
+ }
109
+
110
+ var ticket = tickets[ ticket_id ];
111
+
112
+ if ( 'global' === ticket.mode ) {
113
+ $tickets_lists.find( '.available-stock[data-product-id=' + ticket_id + ']').html( remaining );
114
+ }
115
+
116
+ if ( 'capped' === ticket.mode ) {
117
+ // If x units of global stock have been requested, the effective cap is the actual cap less value x
118
+ var effective_cap = Math.min( remaining, ticket.cap );
119
+ var requested_stock = parseInt( $( '[data-product-id=' + ticket_id + ']' ).find( 'input' ).val(), 10 );
120
+ var remaining_under_cap = ticket.cap - requested_stock;
121
+
122
+ // As with all other ticket types, capped tickets should not have a sub-zero count either
123
+ if ( remaining_under_cap < 0 ) {
124
+ remaining_under_cap = 0;
125
+ }
126
+ // Nor can their count exceed the effective cap
127
+ else if ( remaining_under_cap > effective_cap ) {
128
+ remaining_under_cap = effective_cap;
129
+ }
130
+
131
+ $tickets_lists.find( '.available-stock[data-product-id=' + ticket_id + ']').html( remaining_under_cap );
132
+ }
133
+ }
134
+ };
135
+
136
+ /**
137
+ * Attempts to determine the product ID associated with the passed
138
+ * element.
139
+ *
140
+ * @param element
141
+ *
142
+ * @returns null|string
143
+ */
144
+ my.get_matching_ticket_id = function( element ) {
145
+ // There should be an element close by (parent or grandparent) from which we can
146
+ // obtain the ticket ID
147
+ var $closest_identifier = $( element ).closest( '[data-product-id]' );
148
+
149
+ // Custom or legacy templates may mean this isn't possible - safely bail if necessary
150
+ if ( ! $closest_identifier.length ) {
151
+ return;
152
+ }
153
+
154
+ return $closest_identifier.data( 'product-id' );
155
+ };
156
+
157
+ /**
158
+ * If possible, returns the value of the specified ticket's property or
159
+ * false if it does not exist.
160
+ *
161
+ * @param ticket_id
162
+ * @param property
163
+ *
164
+ * @returns boolean|string
165
+ */
166
+ my.get_ticket_property = function( ticket_id, property ) {
167
+ // Don't trigger errors if tribe_tickets_stock_data is not available
168
+ if ( "object" !== typeof tribe_tickets_stock_data ) {
169
+ return false;
170
+ }
171
+
172
+ var ticket = tribe_tickets_stock_data.tickets[ ticket_id ];
173
+
174
+ // If we don't have any data for this ticket we can assume it doesn't use global stock
175
+ if ( "undefined" === tribe_tickets_stock_data.tickets[ ticket_id ] ) {
176
+ return false;
177
+ }
178
+
179
+ return ticket[property];
180
+ };
181
+
182
+ /**
183
+ * Provides an array of ticket objects that all belong to the specified
184
+ * event.
185
+ *
186
+ * @param event_id
187
+ *
188
+ * @returns Array
189
+ */
190
+ my.get_tickets_of = function( event_id ) {
191
+ // Don't trigger errors if tribe_tickets_stock_data is not available
192
+ if ( "object" !== typeof tribe_tickets_stock_data ) {
193
+ return false;
194
+ }
195
+
196
+ var set_of_tickets = [];
197
+
198
+ for ( var ticket_id in tribe_tickets_stock_data.tickets ) {
199
+ var ticket = tribe_tickets_stock_data.tickets[ ticket_id ];
200
+ if ( event_id === ticket.event_id ) {
201
+ set_of_tickets[ ticket_id ] = ticket;
202
+ }
203
+ }
204
+
205
+ return set_of_tickets;
206
+ };
207
+
208
+ /**
209
+ * Sum of all quantity inputs that have tickets drawing on the event's global stock.
210
+ *
211
+ * @param event_id
212
+ * @returns {number}
213
+ */
214
+ my.currently_requested_global_event_stock = function( event_id ) {
215
+ var total = 0;
216
+ var tickets = my.get_tickets_of( event_id );
217
+
218
+ for ( var ticket_id in tickets ) {
219
+ switch ( tribe_tickets_stock_data.tickets[ticket_id].mode ) {
220
+ case 'global':
221
+ case 'capped':
222
+ total += parseInt( $tickets_lists.find( '[data-product-id=' + ticket_id + ']').find( 'input').val(), 10 );
223
+ break;
224
+ }
225
+ }
226
+
227
+ return total;
228
+ };
229
+
230
+ /**
231
+ * If possible, returns the value of the specified event's property or
232
+ * false if it does not exist.
233
+ *
234
+ * @param event_id
235
+ * @param property
236
+ *
237
+ * @returns boolean|string
238
+ */
239
+ my.get_event_property = function( event_id, property ) {
240
+ // Don't trigger errors if tribe_tickets_stock_data is not available
241
+ if ( "object" !== typeof tribe_tickets_stock_data ) {
242
+ return false;
243
+ }
244
+
245
+ var event = tribe_tickets_stock_data.events[ event_id ];
246
+
247
+ // If we don't have any data for this ticket we can assume it doesn't use global stock
248
+ if ( "undefined" === tribe_tickets_stock_data.events[ event_id ] ) {
249
+ return false;
250
+ }
251
+
252
+ return event[property];
253
+ };
254
+
255
+ my.stock_mode_is_global = function( ticket_id ) {
256
+ return "global" === my.get_mode( ticket_id );
257
+ };
258
+
259
+ my.stock_mode_is_capped = function( ticket_id ) {
260
+ return "capped" === my.get_mode( ticket_id );
261
+ };
262
+
263
+ my.ticket_uses_global_stock = function( ticket_id ) {
264
+ return my.stock_mode_is_capped( ticket_id ) || my.stock_mode_is_global( ticket_id );
265
+ };
266
+
267
+ my.get_mode = function( ticket_id ) {
268
+ return my.get_ticket_property( ticket_id, 'mode' );
269
+ };
270
+
271
+ my.get_event_id = function( ticket_id ) {
272
+ return my.get_ticket_property( ticket_id, 'event_id' );
273
+ };
274
+
275
+ my.get_cap = function( ticket_id ) {
276
+ return my.get_ticket_property( ticket_id, 'cap' );
277
+ };
278
+
279
+ my.get_global_stock = function( event_id ) {
280
+ return my.get_event_property( event_id, 'stock' );
281
+ };
282
+
283
+ my.get_single_stock = function( ticket_id ) {
284
+ return my.get_ticket_property( ticket_id, 'stock' );
285
+ };
286
+
287
+ $( function() {
288
+ my.init();
289
+ } );
290
+ } )( jQuery, tribe_tickets_ticket_form );
src/resources/js/frontend-ticket-form.min.js ADDED
@@ -0,0 +1 @@
 
1
+ var tribe_tickets_ticket_form={};!function(t,e){var _,i;e.init=function(){_=t(".tribe-events-tickets"),i=_.find(".quantity").find("input"),i.on("change",e.on_quantity_change)},e.on_quantity_change=function(){var _=t(this),i=e.get_matching_ticket_id(this);e.ticket_uses_global_stock(i)?e.global_stock_quantity_changed(_,i):e.normal_stock_quantity_changed(_,i)},e.global_stock_quantity_changed=function(t,_){var i=t.val(),c=e.get_event_id(_),a=e.get_cap(_),n=e.get_global_stock(c),o=e.currently_requested_global_event_stock(c);o>n&&(i-=o-n),e.stock_mode_is_capped(_)&&i>a&&(i=a),0>=i&&(i=0),t.val(i),e.update_available_stock_counts(c)},e.normal_stock_quantity_changed=function(t,i){var c,a=t.val(),n=e.get_single_stock(i);a>n&&(a=n),t.val(a),c=n-a,_.find(".available-stock[data-product-id="+i+"]").html(c)},e.update_available_stock_counts=function(i){var c=e.get_tickets_of(i),a=e.get_global_stock(i)-e.currently_requested_global_event_stock(i);for(var n in c)if(c.hasOwnProperty(n)){0>a&&(a=0);var o=c[n];if("global"===o.mode&&_.find(".available-stock[data-product-id="+n+"]").html(a),"capped"===o.mode){var r=Math.min(a,o.cap),s=parseInt(t("[data-product-id="+n+"]").find("input").val(),10),d=o.cap-s;0>d?d=0:d>r&&(d=r),_.find(".available-stock[data-product-id="+n+"]").html(d)}}},e.get_matching_ticket_id=function(e){var _=t(e).closest("[data-product-id]");if(_.length)return _.data("product-id")},e.get_ticket_property=function(t,e){if("object"!=typeof tribe_tickets_stock_data)return!1;var _=tribe_tickets_stock_data.tickets[t];return"undefined"===tribe_tickets_stock_data.tickets[t]?!1:_[e]},e.get_tickets_of=function(t){if("object"!=typeof tribe_tickets_stock_data)return!1;var e=[];for(var _ in tribe_tickets_stock_data.tickets){var i=tribe_tickets_stock_data.tickets[_];t===i.event_id&&(e[_]=i)}return e},e.currently_requested_global_event_stock=function(t){var i=0,c=e.get_tickets_of(t);for(var a in c)switch(tribe_tickets_stock_data.tickets[a].mode){case"global":case"capped":i+=parseInt(_.find("[data-product-id="+a+"]").find("input").val(),10)}return i},e.get_event_property=function(t,e){if("object"!=typeof tribe_tickets_stock_data)return!1;var _=tribe_tickets_stock_data.events[t];return"undefined"===tribe_tickets_stock_data.events[t]?!1:_[e]},e.stock_mode_is_global=function(t){return"global"===e.get_mode(t)},e.stock_mode_is_capped=function(t){return"capped"===e.get_mode(t)},e.ticket_uses_global_stock=function(t){return e.stock_mode_is_capped(t)||e.stock_mode_is_global(t)},e.get_mode=function(t){return e.get_ticket_property(t,"mode")},e.get_event_id=function(t){return e.get_ticket_property(t,"event_id")},e.get_cap=function(t){return e.get_ticket_property(t,"cap")},e.get_global_stock=function(t){return e.get_event_property(t,"stock")},e.get_single_stock=function(t){return e.get_ticket_property(t,"stock")},t(function(){e.init()})}(jQuery,tribe_tickets_ticket_form);
src/resources/js/rsvp.js CHANGED
@@ -11,6 +11,9 @@ var tribe_tickets_rsvp = {
11
  this.attendee_template = $( document.getElementById( 'tribe-tickets-rsvp-tmpl' ) ).html();
12
 
13
  this.$rsvp.on( 'change', '.tribe-ticket-quantity', this.event.quantity_changed );
 
 
 
14
  };
15
 
16
  my.quantity_changed = function( $quantity ) {
@@ -25,10 +28,36 @@ var tribe_tickets_rsvp = {
25
  }
26
  };
27
 
 
 
 
 
 
 
 
 
 
 
 
28
  my.event.quantity_changed = function() {
29
  my.quantity_changed( $( this ) );
30
  };
31
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
32
  $( function() {
33
  my.init();
34
  } );
11
  this.attendee_template = $( document.getElementById( 'tribe-tickets-rsvp-tmpl' ) ).html();
12
 
13
  this.$rsvp.on( 'change', '.tribe-ticket-quantity', this.event.quantity_changed );
14
+
15
+ this.$rsvp.closest( '.cart' )
16
+ .on( 'submit', this.event.handle_submission );
17
  };
18
 
19
  my.quantity_changed = function( $quantity ) {
28
  }
29
  };
30
 
31
+ my.validate_submission = function() {
32
+ var $name = $( document.getElementById( 'tribe-tickets-full-name' ) );
33
+ var $email = $( document.getElementById( 'tribe-tickets-email' ) );
34
+
35
+ if ( ! $.trim( $name.val() ).length || ! $.trim( $email.val() ).length ) {
36
+ return false;
37
+ }
38
+
39
+ return true;
40
+ };
41
+
42
  my.event.quantity_changed = function() {
43
  my.quantity_changed( $( this ) );
44
  };
45
 
46
+ my.event.handle_submission = function( e ) {
47
+ if ( ! my.validate_submission() ) {
48
+ e.preventDefault();
49
+ var $form = $( this ).closest( 'form' );
50
+
51
+ $form.addClass( 'tribe-rsvp-message-display' );
52
+ $form.find( '.tribe-rsvp-message-confirmation-error' ).show();
53
+
54
+ $( 'html, body').animate({
55
+ scrollTop: $form.offset().top
56
+ }, 300 );
57
+ return false;
58
+ }
59
+ };
60
+
61
  $( function() {
62
  my.init();
63
  } );
src/resources/js/rsvp.min.js CHANGED
@@ -1 +1 @@
1
- var tribe_tickets_rsvp={num_attendees:0,event:{}};!function(a,b){"use strict";b.init=function(){this.$rsvp=a(".tribe-events-tickets-rsvp"),this.attendee_template=a(document.getElementById("tribe-tickets-rsvp-tmpl")).html(),this.$rsvp.on("change",".tribe-ticket-quantity",this.event.quantity_changed)},b.quantity_changed=function(a){var b=a.closest(".tribe-events-tickets-rsvp"),c=parseInt(a.val(),10);c?b.addClass("tribe-tickets-has-rsvp"):b.removeClass("tribe-tickets-has-rsvp")},b.event.quantity_changed=function(){b.quantity_changed(a(this))},a(function(){b.init()})}(jQuery,tribe_tickets_rsvp);
1
+ var tribe_tickets_rsvp={num_attendees:0,event:{}};!function(t,e){"use strict";e.init=function(){this.$rsvp=t(".tribe-events-tickets-rsvp"),this.attendee_template=t(document.getElementById("tribe-tickets-rsvp-tmpl")).html(),this.$rsvp.on("change",".tribe-ticket-quantity",this.event.quantity_changed),this.$rsvp.closest(".cart").on("submit",this.event.handle_submission)},e.quantity_changed=function(t){var e=t.closest(".tribe-events-tickets-rsvp"),s=parseInt(t.val(),10);s?e.addClass("tribe-tickets-has-rsvp"):e.removeClass("tribe-tickets-has-rsvp")},e.validate_submission=function(){var e=t(document.getElementById("tribe-tickets-full-name")),s=t(document.getElementById("tribe-tickets-email"));return!(!t.trim(e.val()).length||!t.trim(s.val()).length)},e.event.quantity_changed=function(){e.quantity_changed(t(this))},e.event.handle_submission=function(s){if(!e.validate_submission()){s.preventDefault();var i=t(this).closest("form");return i.addClass("tribe-rsvp-message-display"),i.find(".tribe-rsvp-message-confirmation-error").show(),t("html, body").animate({scrollTop:i.offset().top},300),!1}},t(function(){e.init()})}(jQuery,tribe_tickets_rsvp);
src/resources/js/tickets-attendees.min.js CHANGED
@@ -1 +1 @@
1
- jQuery(document).ready(function(a){if(AttendeesPointer){options=a.extend(AttendeesPointer.options,{close:function(){a.post(ajaxurl,{pointer:AttendeesPointer.pointer_id,action:"dismiss-wp-pointer"})},open:function(a,b){b.pointer.css({top:parseInt(b.pointer.css("top").replace("px",""),10)+5}).find(".wp-pointer-arrow").css({right:"50px",left:"auto"}),b.element.on({click:function(){b.element.pointer("close")}})}});a(AttendeesPointer.target).pointer(options).pointer("open").pointer("widget")}a("input.print").on("click",function(a){window.print()});var b=a(document.getElementById("filter_attendee"));b.on("keydown",function(a){return 13===a.keyCode?!1:void 0}),b.on("keyup paste",function(){var b=jQuery(this).val().toLowerCase();a("#the-list").find("tr").each(function(c,d){var e=a(d),f=e.children("td.order_id").children("a").text(),g=e.children("td.attendee_id").text(),h=e.children("td.security").text(),i=0===g.indexOf(b)||0===f.indexOf(b)||0===h.indexOf(b),j=e.children("td.purchaser_name").text().toLowerCase(),k=0===j.indexOf(b)||j.indexOf(" "+b)>1;i||k?e.show():e.hide()})}),a(".tribe-attendees-email").on({submit:function(b){a(".tribe-attendees-email").hide(),a(document.getElementById("tribe-loading")).show()}}),a(".tickets_checkin").click(function(b){var c=jQuery(this),d={action:"tribe-ticket-checkin-"+c.attr("data-provider"),provider:c.attr("data-provider"),order_ID:c.attr("data-attendee-id"),nonce:Attendees.checkin_nonce};a.post(ajaxurl,d,function(b){b.success&&(c.parent("td").parent("tr").addClass("tickets_checked"),a("#total_checkedin").text(parseInt(a("#total_checkedin").text())+1))},"json"),b.preventDefault()}),a(".tickets_uncheckin").click(function(b){var c=jQuery(this),d={action:"tribe-ticket-uncheckin-"+c.attr("data-provider"),provider:c.attr("data-provider"),order_ID:c.attr("data-attendee-id"),nonce:Attendees.uncheckin_nonce};a.post(ajaxurl,d,function(b){b.success&&(c.parent("span").parent("td").parent("tr").removeClass("tickets_checked"),a("#total_checkedin").text(parseInt(a("#total_checkedin").text())-1))},"json"),b.preventDefault()})});
1
+ jQuery(document).ready(function(e){if(AttendeesPointer){options=e.extend(AttendeesPointer.options,{close:function(){e.post(ajaxurl,{pointer:AttendeesPointer.pointer_id,action:"dismiss-wp-pointer"})},open:function(e,t){t.pointer.css({top:parseInt(t.pointer.css("top").replace("px",""),10)+5}).find(".wp-pointer-arrow").css({right:"50px",left:"auto"}),t.element.on({click:function(){t.element.pointer("close")}})}});e(AttendeesPointer.target).pointer(options).pointer("open").pointer("widget")}e("input.print").on("click",function(e){window.print()});var t=e(document.getElementById("filter_attendee"));t.on("keydown",function(e){return 13===e.keyCode?!1:void 0}),t.on("keyup paste",function(){var t=jQuery(this).val().toLowerCase();e("#the-list").find("tr").each(function(n,i){var r=e(i),o=r.children("td.order_id").children("a").text(),c=r.children("td.attendee_id").text(),d=r.children("td.security").text(),a=0===c.indexOf(t)||0===o.indexOf(t)||0===d.indexOf(t),s=r.children("td.purchaser_name").text().toLowerCase(),p=0===s.indexOf(t)||s.indexOf(" "+t)>1;a||p?r.show():r.hide()})}),e(".tribe-attendees-email").on({submit:function(t){e(".tribe-attendees-email").hide(),e(document.getElementById("tribe-loading")).show()}}),e(".tickets_checkin").click(function(t){var n=jQuery(this),i={action:"tribe-ticket-checkin-"+n.attr("data-provider"),provider:n.attr("data-provider"),order_ID:n.attr("data-attendee-id"),nonce:Attendees.checkin_nonce};e.post(ajaxurl,i,function(t){t.success&&(n.parent("td").parent("tr").addClass("tickets_checked"),e("#total_checkedin").text(parseInt(e("#total_checkedin").text())+1))},"json"),t.preventDefault()}),e(".tickets_uncheckin").click(function(t){var n=jQuery(this),i={action:"tribe-ticket-uncheckin-"+n.attr("data-provider"),provider:n.attr("data-provider"),order_ID:n.attr("data-attendee-id"),nonce:Attendees.uncheckin_nonce};e.post(ajaxurl,i,function(t){t.success&&(n.parent("span").parent("td").parent("tr").removeClass("tickets_checked"),e("#total_checkedin").text(parseInt(e("#total_checkedin").text())-1))},"json"),t.preventDefault()})});
src/resources/js/tickets.js CHANGED
@@ -47,6 +47,9 @@ var ticketHeaderImage = window.ticketHeaderImage || {};
47
  var $event_pickers = $( '#tribe-event-datepickers' ),
48
  $tribe_tickets = $( '#tribetickets' ),
49
  $tickets_container = $( '#event_tickets' ),
 
 
 
50
  $body = $( 'html, body' ),
51
  startofweek = 0;
52
 
@@ -81,13 +84,16 @@ var ticketHeaderImage = window.ticketHeaderImage || {};
81
  */
82
  'clear.tribe': function() {
83
  var $this = $( this ),
84
- $ticket_form = $this.find( '#ticket_form' );
 
85
 
86
  $this.find( 'a#ticket_form_toggle' ).show();
87
 
88
- $this.find( 'input:not(:button):not(:radio):not(:checkbox):not([type="hidden"]), textarea' ).val( '' );
89
- $this.find( 'input:checkbox' ).attr( 'checked', false );
90
- $this.find( '#ticket_id' ).val( '' );
 
 
91
 
92
  // some fields may have a default value we don't want to lose after clearing the form
93
  $this.find( 'input[data-default-value]' ).each( function() {
@@ -105,6 +111,9 @@ var ticketHeaderImage = window.ticketHeaderImage || {};
105
  .siblings( '.no-update-message' ).html( '' ).hide()
106
  .end().siblings( '.description' ).show();
107
 
 
 
 
108
  $ticket_form.hide();
109
  },
110
 
@@ -128,7 +137,7 @@ var ticketHeaderImage = window.ticketHeaderImage || {};
128
  'set-advanced-fields.tribe': function() {
129
  var $this = $( this );
130
  var $ticket_form = $this.find( '#ticket_form' );
131
- var $ticket_advanced = $ticket_form.find( 'tr.ticket_advanced' ).find( 'input, select, textarea' );
132
  var provider = $ticket_form.find( '#ticket_provider:checked' ).val();
133
 
134
  // for each advanded ticket input, select, and textarea, relocate the name and id fields a bit
@@ -155,8 +164,57 @@ var ticketHeaderImage = window.ticketHeaderImage || {};
155
  } );
156
  }
157
  } );
158
- }
159
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
160
  } );
161
 
162
  if ( $event_pickers.length ) {
@@ -207,20 +265,61 @@ var ticketHeaderImage = window.ticketHeaderImage || {};
207
  }
208
  } );
209
 
210
- /* Show the advanced metabox for the selected provider and hide the others on selection change */
211
- $( 'input[name=ticket_provider]:radio' ).change( function() {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
212
  $( 'tr.ticket_advanced' ).hide();
 
213
  $tribe_tickets.trigger( 'set-advanced-fields.tribe' );
214
- $( 'tr.ticket_advanced_' + this.value + ':not(.sale_price)' ).show();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
215
  } );
216
 
217
  /* Show the advanced metabox for the selected provider and hide the others at ready */
218
  $( 'input[name=ticket_provider]:checked' ).each( function() {
219
- var $ticket_advanced = $( 'tr.ticket_advanced' );
220
- $ticket_advanced.hide()
221
- .filter( 'tr.ticket_advanced_' + this.value )
222
- .not( '.sale_price' )
223
- .show();
224
  } );
225
 
226
  /* "Add a ticket" link action */
@@ -233,6 +332,7 @@ var ticketHeaderImage = window.ticketHeaderImage || {};
233
  .trigger( 'set-advanced-fields.tribe' )
234
  .trigger( 'focus.tribe' );
235
  $( '#ticket_form' ).show();
 
236
  e.preventDefault();
237
  } );
238
 
@@ -248,7 +348,7 @@ var ticketHeaderImage = window.ticketHeaderImage || {};
248
  $( '#ticket_form_save' ).click( function( e ) {
249
  var $form = $( '#ticket_form_table' ),
250
  type = $form.find( '#ticket_provider:checked' ).val(),
251
- $rows = $form.find( '.ticket, .ticket_advanced_' + type );
252
 
253
  $tribe_tickets.trigger( 'save-ticket.tribe', e ).trigger( 'spin.tribe', 'start' );
254
 
@@ -267,7 +367,7 @@ var ticketHeaderImage = window.ticketHeaderImage || {};
267
 
268
  if ( response.success ) {
269
  $tribe_tickets.trigger( 'clear.tribe' );
270
- $( 'td.ticket_list_container' ).empty().html( response.data );
271
  $( '.ticket_time' ).hide();
272
  }
273
  },
@@ -429,9 +529,13 @@ var ticketHeaderImage = window.ticketHeaderImage || {};
429
  'name': '',
430
  'id': ''
431
  } );
432
- $( 'tr.ticket_advanced_' + response.data.provider_class ).remove();
433
  $( 'tr.ticket.bottom' ).before( response.data.advanced_fields );
434
 
 
 
 
 
435
  // set the prices after the advanced fields have been added to the form
436
  var $ticket_price = $tribe_tickets.find( '#ticket_price' );
437
  $ticket_price.val( regularPrice );
@@ -467,14 +571,16 @@ var ticketHeaderImage = window.ticketHeaderImage || {};
467
  $( '#ticket_purchase_limit' ).val( response.data.purchase_limit );
468
  }
469
 
470
- $( 'input:radio[name=ticket_provider]' ).filter( '[value=' + response.data.provider_class + ']' ).click();
471
-
472
  $tribe_tickets.find( '.bumpdown-trigger' ).bumpdown();
473
  $tribe_tickets.find( '.bumpdown' ).hide();
474
 
475
  $( 'a#ticket_form_toggle' ).hide();
476
  $( '#ticket_form' ).show();
477
 
 
 
 
 
478
  },
479
  'json'
480
  ).complete( function() {
@@ -495,6 +601,39 @@ var ticketHeaderImage = window.ticketHeaderImage || {};
495
  $remove.show();
496
  }
497
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
498
  $('body').on( 'click', '#tribe_ticket_header_remove', function( e ) {
499
 
500
  e.preventDefault();
47
  var $event_pickers = $( '#tribe-event-datepickers' ),
48
  $tribe_tickets = $( '#tribetickets' ),
49
  $tickets_container = $( '#event_tickets' ),
50
+ $enable_global_stock = $( "#tribe-tickets-enable-global-stock" ),
51
+ $global_stock_level = $( "#tribe-tickets-global-stock-level" ),
52
+ global_stock_setting_changed = false,
53
  $body = $( 'html, body' ),
54
  startofweek = 0;
55
 
84
  */
85
  'clear.tribe': function() {
86
  var $this = $( this ),
87
+ $ticket_form = $this.find( '#ticket_form'),
88
+ $ticket_settings = $ticket_form.find( "tr:not(.event-wide-settings)" );
89
 
90
  $this.find( 'a#ticket_form_toggle' ).show();
91
 
92
+ $ticket_settings.find( 'input:not(:button):not(:radio):not(:checkbox):not([type="hidden"]), textarea' ).val( '' );
93
+ $ticket_settings.find( 'input:checkbox' ).attr( 'checked', false );
94
+ $ticket_settings.find( '#ticket_id' ).val( '' );
95
+
96
+ $this.find( '#ticket_form input[name="show_attendee_info"]' ).prop( 'checked', false ).change();
97
 
98
  // some fields may have a default value we don't want to lose after clearing the form
99
  $this.find( 'input[data-default-value]' ).each( function() {
111
  .siblings( '.no-update-message' ).html( '' ).hide()
112
  .end().siblings( '.description' ).show();
113
 
114
+ $('#tribe-tickets-attendee-sortables').empty();
115
+ $('.tribe-tickets-attendee-saved-fields').show();
116
+
117
  $ticket_form.hide();
118
  },
119
 
137
  'set-advanced-fields.tribe': function() {
138
  var $this = $( this );
139
  var $ticket_form = $this.find( '#ticket_form' );
140
+ var $ticket_advanced = $ticket_form.find( 'tr.ticket_advanced:not(.ticket_advanced_meta)' ).find( 'input, select, textarea' );
141
  var provider = $ticket_form.find( '#ticket_provider:checked' ).val();
142
 
143
  // for each advanded ticket input, select, and textarea, relocate the name and id fields a bit
164
  } );
165
  }
166
  } );
 
167
 
168
+ // (Re-)set the global stock fields
169
+ $tribe_tickets.trigger( 'set-global-stock-fields.tribe' );
170
+
171
+ // Also reset each time the global stock mode selector is changed
172
+ $( '#ticket_global_stock' ).change( function() {
173
+ $tribe_tickets.trigger( 'set-global-stock-fields.tribe' );
174
+ });
175
+ },
176
+
177
+ 'set-global-stock-fields.tribe': function() {
178
+ var provider_class = currently_selected_provider();
179
+ var $provider_fields = $( this ).find( '#ticket_form').find( '.ticket_advanced_' + provider_class );
180
+
181
+ if ( $provider_fields.length < 1 ) {
182
+ return;
183
+ }
184
+
185
+ var $normal_stock_field = $provider_fields.filter( '.stock' );
186
+ var $global_stock_fields = $provider_fields.filter( '.global-stock-mode' );
187
+ var $sales_cap_field = $global_stock_fields.filter( '.sales-cap-field' );
188
+
189
+ var mode = $( '#ticket_global_stock' ).val();
190
+ var enabled = global_stock_enabled();
191
+
192
+ // Show or hide global (and normal, "per-ticket") stock settings as appropriate
193
+ $global_stock_level.toggle( enabled );
194
+ $global_stock_fields.toggle( global_stock_enabled() );
195
+ $normal_stock_field.toggle( ! enabled );
196
+
197
+ // If global stock is not enabled we need go no further
198
+ if ( ! enabled ) {
199
+ return;
200
+ }
201
+
202
+ // Otherwise, toggle on and off the relevant stock quantity fields
203
+ switch ( mode ) {
204
+ case "global":
205
+ $sales_cap_field.hide();
206
+ $normal_stock_field.hide();
207
+ break;
208
+ case "capped":
209
+ $sales_cap_field.show();
210
+ $normal_stock_field.hide();
211
+ break;
212
+ case "own":
213
+ $sales_cap_field.hide();
214
+ $normal_stock_field.show();
215
+ break;
216
+ }
217
+ }
218
  } );
219
 
220
  if ( $event_pickers.length ) {
265
  }
266
  } );
267
 
268
+ /**
269
+ * Indicates if the "enable global stock" field has been checked.
270
+ *
271
+ * @returns boolean
272
+ */
273
+ function global_stock_enabled() {
274
+ return $enable_global_stock.prop( "checked" );
275
+ }
276
+
277
+ /**
278
+ * Show or hide global stock fields and settings as appropriate.
279
+ */
280
+ function show_hide_global_stock() {
281
+ global_stock_setting_changed = true;
282
+ $tribe_tickets.trigger( 'set-global-stock-fields.tribe' );
283
+ }
284
+
285
+ /**
286
+ * Show or hide the appropriate set of provider-specific fields.
287
+ */
288
+ function show_hide_advanced_fields() {
289
  $( 'tr.ticket_advanced' ).hide();
290
+ $( 'tr.ticket_advanced_' + currently_selected_provider() + ':not(.sale_price)' ).show();
291
  $tribe_tickets.trigger( 'set-advanced-fields.tribe' );
292
+ $( document.getElementById( 'tribetickets' ) ).trigger( 'ticket-provider-changed.tribe' );
293
+ }
294
+
295
+ /**
296
+ * Returns the currently selected ticketing provider.
297
+ *
298
+ * @return string
299
+ */
300
+ function currently_selected_provider() {
301
+ var $checked_provider = $( 'input[name="ticket_provider"]:checked' );
302
+ return ( $checked_provider.length > 0 )
303
+ ? $checked_provider[0].value
304
+ : "";
305
+ }
306
+
307
+ // Show or hide the global stock level as appropriate, both initially and thereafter
308
+ $enable_global_stock.change( show_hide_global_stock );
309
+ $enable_global_stock.trigger( 'change' );
310
+
311
+ // Triggering a change event will falsely set the global_stock_setting_changed flag to
312
+ // true - undo this as it is a one-time false positive
313
+ global_stock_setting_changed = false;
314
+
315
+ /* Show the advanced metabox for the selected provider and hide the others on selection change */
316
+ $( 'input[name=ticket_provider]:radio' ).change( function() {
317
+ show_hide_advanced_fields();
318
  } );
319
 
320
  /* Show the advanced metabox for the selected provider and hide the others at ready */
321
  $( 'input[name=ticket_provider]:checked' ).each( function() {
322
+ show_hide_advanced_fields();
 
 
 
 
323
  } );
324
 
325
  /* "Add a ticket" link action */
332
  .trigger( 'set-advanced-fields.tribe' )
333
  .trigger( 'focus.tribe' );
334
  $( '#ticket_form' ).show();
335
+ $( document.getElementById( 'tribetickets' ) ).trigger( 'ticket-provider-changed.tribe' );
336
  e.preventDefault();
337
  } );
338
 
348
  $( '#ticket_form_save' ).click( function( e ) {
349
  var $form = $( '#ticket_form_table' ),
350
  type = $form.find( '#ticket_provider:checked' ).val(),
351
+ $rows = $form.find( '.ticket, .ticket_advanced_meta, .ticket_advanced_' + type );
352
 
353
  $tribe_tickets.trigger( 'save-ticket.tribe', e ).trigger( 'spin.tribe', 'start' );
354
 
367
 
368
  if ( response.success ) {
369
  $tribe_tickets.trigger( 'clear.tribe' );
370
+ $( 'td.ticket_list_container' ).empty().html( response.data.html );
371
  $( '.ticket_time' ).hide();
372
  }
373
  },
529
  'name': '',
530
  'id': ''
531
  } );
532
+ $( 'tr.ticket_advanced' ).remove();
533
  $( 'tr.ticket.bottom' ).before( response.data.advanced_fields );
534
 
535
+ // trigger a change event on the provider radio input so the advanced fields can be re-initialized
536
+ $( 'input:radio[name=ticket_provider]' ).filter( '[value=' + response.data.provider_class + ']' ).click();
537
+ $( 'input[name=ticket_provider]:radio' ).change();
538
+
539
  // set the prices after the advanced fields have been added to the form
540
  var $ticket_price = $tribe_tickets.find( '#ticket_price' );
541
  $ticket_price.val( regularPrice );
571
  $( '#ticket_purchase_limit' ).val( response.data.purchase_limit );
572
  }
573
 
 
 
574
  $tribe_tickets.find( '.bumpdown-trigger' ).bumpdown();
575
  $tribe_tickets.find( '.bumpdown' ).hide();
576
 
577
  $( 'a#ticket_form_toggle' ).hide();
578
  $( '#ticket_form' ).show();
579
 
580
+ $tribe_tickets
581
+ .trigger( 'set-advanced-fields.tribe' )
582
+ .trigger( 'edit-ticket.tribe' );
583
+
584
  },
585
  'json'
586
  ).complete( function() {
601
  $remove.show();
602
  }
603
 
604
+ /**
605
+ * Track changes to the global stock level. Changes to the global stock
606
+ * checkbox itself is handled elsewhere.
607
+ */
608
+ $global_stock_level.change( function() {
609
+ global_stock_setting_changed = true;
610
+ } );
611
+
612
+ /**
613
+ * Unset the global stock settings changed flag if the post is being
614
+ * saved/updated (no need to trigger a confirmation dialog in these
615
+ * cases).
616
+ */
617
+ $( 'input[type="submit"]' ).click( function() {
618
+ global_stock_setting_changed = false;
619
+ } );
620
+
621
+ /**
622
+ * If the user attempts to nav away without saving global stock setting
623
+ * changes then try to bring this to their attention!
624
+ */
625
+ $( window ).on( 'beforeunload', function() {
626
+ // If the global stock settings have not changed, do not interfere
627
+ if ( ! global_stock_setting_changed ) {
628
+ return;
629
+ }
630
+
631
+ // We can't trigger a confirm() dialog from within this action but returning
632
+ // a string should achieve effectively the same result
633
+ return tribe_global_stock_admin_ui.nav_away_msg;
634
+
635
+ } );
636
+
637
  $('body').on( 'click', '#tribe_ticket_header_remove', function( e ) {
638
 
639
  e.preventDefault();
src/resources/js/tickets.min.js CHANGED
@@ -1 +1 @@
1
- var ticketHeaderImage=window.ticketHeaderImage||{};!function(a,b,c){"use strict";ticketHeaderImage={uploader:function(){var a=wp.media({title:HeaderImageData.title,multiple:!1,library:{type:"image"},button:{text:HeaderImageData.button}});return a.on("close",function(){var b=a.state().get("selection").toJSON();b.length&&ticketHeaderImage.render(b[0])}),a.open(),!1},render:function(a){b("#tribe_ticket_header_preview").html(ticketHeaderImage.imgHTML(a)),b("#tribe_ticket_header_image_id").val(a.id),b("#tribe_ticket_header_remove").show()},imgHTML:function(a){var b='<img src="'+a.url+'" ';return b+='width="'+a.width+'" ',b+='height="'+a.height+'" ',b+="/>"}},b(document).ready(function(){var a=b("#tribe-event-datepickers"),c=b("#tribetickets"),d=b("#event_tickets"),e=b("html, body"),f=0;c.on({"spin.tribe":function(a,c){("undefined"==typeof c||b.inArray(c,["start","stop"]))&&(c="stop"),"stop"===c?d.css("opacity","1").find("#tribe-loading").hide():d.css("opacity","0.5").find("#tribe-loading").show()},"clear.tribe":function(){var a=b(this),c=a.find("#ticket_form");a.find("a#ticket_form_toggle").show(),a.find('input:not(:button):not(:radio):not(:checkbox):not([type="hidden"]), textarea').val(""),a.find("input:checkbox").attr("checked",!1),a.find("#ticket_id").val(""),a.find("input[data-default-value]").each(function(){var a=b(this);a.val(a.data("default-value"))}),a.find("#ticket_start_date").datepicker("option","maxDate",null),a.find("#ticket_end_date").datepicker("option","minDate",null),a.find(".ticket_start_time, .ticket_end_time, .ticket.sale_price").hide(),a.find("#ticket_price").removeProp("disabled").siblings(".no-update-message").html("").hide().end().siblings(".description").show(),c.hide()},"focus.tribe":function(){e.animate({scrollTop:d.offset().top-50},500)},"set-advanced-fields.tribe":function(){var a=b(this),c=a.find("#ticket_form"),d=c.find("tr.ticket_advanced").find("input, select, textarea"),e=c.find("#ticket_provider:checked").val();d.each(function(){var a=b(this);a.attr("name")&&a.data("name",a.attr("name")).attr({name:"",id:""}),a.closest("tr").hasClass("ticket_advanced_"+e)&&a.data("name")&&0===a.attr("name").length&&a.attr({name:a.data("name"),id:a.data("name")})})}}),a.length&&(f=a.data("startofweek"));var g={dateFormat:"yy-mm-dd",showAnim:"fadeIn",changeMonth:!0,changeYear:!0,numberOfMonths:3,firstDay:f,showButtonPanel:!0,onChange:function(){},onSelect:function(a,c){var d=b.datepicker.parseDate("yy-mm-dd",a);"ticket_start_date"===c.id?(b("#ticket_end_date").datepicker("option","minDate",d),d?b(".ticket_start_time").show():b(".ticket_start_time").hide()):(b("#ticket_start_date").datepicker("option","maxDate",d),d?b(".ticket_end_time").show():b(".ticket_end_time").hide())}};b("#ticket_start_date").datepicker(g).keyup(function(a){(8===a.keyCode||46===a.keyCode)&&b.datepicker._clearDate(this)}),b("#ticket_end_date").datepicker(g).keyup(function(a){(8===a.keyCode||46===a.keyCode)&&b.datepicker._clearDate(this)}),b("input[name=ticket_provider]:radio").change(function(){b("tr.ticket_advanced").hide(),c.trigger("set-advanced-fields.tribe"),b("tr.ticket_advanced_"+this.value+":not(.sale_price)").show()}),b("input[name=ticket_provider]:checked").each(function(){var a=b("tr.ticket_advanced");a.hide().filter("tr.ticket_advanced_"+this.value).not(".sale_price").show()}),b("a#ticket_form_toggle").click(function(a){b("h4.ticket_form_title_edit").hide(),b("h4.ticket_form_title_add").show(),b(this).hide(),c.trigger("clear.tribe").trigger("set-advanced-fields.tribe").trigger("focus.tribe"),b("#ticket_form").show(),a.preventDefault()}),b("#ticket_form_cancel").click(function(){c.trigger("clear.tribe").trigger("set-advanced-fields.tribe").trigger("focus.tribe")}),b("#ticket_form_save").click(function(a){var d=b("#ticket_form_table"),e=d.find("#ticket_provider:checked").val(),f=d.find(".ticket, .ticket_advanced_"+e);c.trigger("save-ticket.tribe",a).trigger("spin.tribe","start");var g={action:"tribe-ticket-add-"+b("input[name=ticket_provider]:checked").val(),formdata:f.find(".ticket_field").serialize(),post_ID:b("#post_ID").val(),nonce:TribeTickets.add_ticket_nonce};b.post(ajaxurl,g,function(a){c.trigger("saved-ticket.tribe",a),a.success&&(c.trigger("clear.tribe"),b("td.ticket_list_container").empty().html(a.data),b(".ticket_time").hide())},"json").complete(function(){c.trigger("spin.tribe","stop").trigger("focus.tribe")})}),c.on("click",".ticket_delete",function(a){a.preventDefault(),c.trigger("delete-ticket.tribe",a).trigger("spin.tribe","start");var d={action:"tribe-ticket-delete-"+b(this).attr("attr-provider"),post_ID:b("#post_ID").val(),ticket_id:b(this).attr("attr-ticket-id"),nonce:TribeTickets.remove_ticket_nonce};b.post(ajaxurl,d,function(a){c.trigger("deleted-ticket.tribe",a),a.success&&(c.trigger("clear.tribe"),b("td.ticket_list_container").empty().html(a.data))},"json").complete(function(){c.trigger("spin.tribe","stop")})}),c.on("click",".ticket_edit",function(a){a.preventDefault(),b("h4.ticket_form_title_edit").show(),b("h4.ticket_form_title_add").hide(),c.trigger("spin.tribe","start");var d={action:"tribe-ticket-edit-"+b(this).attr("attr-provider"),post_ID:b("#post_ID").val(),ticket_id:b(this).attr("attr-ticket-id"),nonce:TribeTickets.edit_ticket_nonce};b.post(ajaxurl,d,function(a){c.trigger("clear.tribe").trigger("set-advanced-fields.tribe").trigger("edit-ticket.tribe",a);var d=a.data.price,e=d,f=!1;"undefined"!=typeof a.data.on_sale&&a.data.on_sale&&(f=!0,d=a.data.regular_price),b("#ticket_id").val(a.data.ID),b("#ticket_name").val(a.data.name),b("#ticket_description").val(a.data.description),f&&b(".ticket_advanced_"+a.data.provider_class+".sale_price").show();var g=a.data.start_date.substring(0,10),h=a.data.end_date.substring(0,10);b("#ticket_start_date").val(g),b("#ticket_end_date").val(h);var i=b(document.getElementById("ticket_start_meridian")),j=b(document.getElementById("ticket_end_meridian"));if(a.data.start_date){var k=parseInt(a.data.start_date.substring(11,13)),l="am";k>12&&i.length&&(l="pm",k=parseInt(k)-12,k=("0"+k).slice(-2)),12===k&&(l="pm"),0===k&&"am"===l&&(k=12),k=k.toString(),1===k.length&&(k="0"+k),b("#ticket_start_hour").val(k),b("#ticket_start_meridian").val(l),b(".ticket_start_time").show()}if(a.data.end_date){var m=parseInt(a.data.end_date.substring(11,13)),n="am";m>12&&j.length&&(n="pm",m=parseInt(m)-12,m=("0"+m).slice(-2)),12===m&&(n="pm"),0===m&&"am"===n&&(m=12),m=m.toString(),1===m.length&&(m="0"+m),b("#ticket_end_hour").val(m),b("#ticket_end_meridian").val(n),b("#ticket_start_minute").val(a.data.start_date.substring(14,16)),b("#ticket_end_minute").val(a.data.end_date.substring(14,16)),b(".ticket_end_time").show()}var o=b("tr.ticket_advanced input");o.data("name",o.attr("name")).attr({name:"",id:""}),b("tr.ticket_advanced_"+a.data.provider_class).remove(),b("tr.ticket.bottom").before(a.data.advanced_fields);var p=c.find("#ticket_price");p.val(d),"undefined"!=typeof a.data.disallow_update_price_message?p.siblings(".no-update-message").html(a.data.disallow_update_price_message):p.siblings(".no-update-message").html(""),"undefined"==typeof a.data.can_update_price||a.data.can_update_price?(p.removeProp("disabled"),p.siblings(".description").show(),p.siblings(".no-update-message").hide()):(p.prop("disabled","disabled"),p.siblings(".description").hide(),p.siblings(".no-update-message").show());var q=c.find("#ticket_sale_price");f?q.val(e).closest("tr").show():q.closest("tr").hide(),"undefined"!=typeof a.data.purchase_limit&&a.data.purchase_limit&&b("#ticket_purchase_limit").val(a.data.purchase_limit),b("input:radio[name=ticket_provider]").filter("[value="+a.data.provider_class+"]").click(),c.find(".bumpdown-trigger").bumpdown(),c.find(".bumpdown").hide(),b("a#ticket_form_toggle").hide(),b("#ticket_form").show()},"json").complete(function(){c.trigger("spin.tribe","stop").trigger("focus.tribe")})}).on("click","#tribe_ticket_header_image",function(a){a.preventDefault(),ticketHeaderImage.uploader("","")});var h=b("#tribe_ticket_header_remove"),i=b("#tribe_ticket_header_preview");if(i.find("img").length&&h.show(),b("body").on("click","#tribe_ticket_header_remove",function(a){a.preventDefault(),i.html(""),h.hide(),b("#tribe_ticket_header_image_id").val("")}),b("#tribe_ticket_header_preview img").length){var j=b("#tribe_ticket_header_preview img");j.removeAttr("width").removeAttr("height"),c.width()<j.width()&&j.css("width","95%")}})}(window,jQuery);
1
+ var ticketHeaderImage=window.ticketHeaderImage||{};!function(t,e,i){"use strict";ticketHeaderImage={uploader:function(){var t=wp.media({title:HeaderImageData.title,multiple:!1,library:{type:"image"},button:{text:HeaderImageData.button}});return t.on("close",function(){var e=t.state().get("selection").toJSON();e.length&&ticketHeaderImage.render(e[0])}),t.open(),!1},render:function(t){e("#tribe_ticket_header_preview").html(ticketHeaderImage.imgHTML(t)),e("#tribe_ticket_header_image_id").val(t.id),e("#tribe_ticket_header_remove").show()},imgHTML:function(t){var e='<img src="'+t.url+'" ';return e+='width="'+t.width+'" ',e+='height="'+t.height+'" ',e+="/>"}},e(document).ready(function(){function i(){return s.prop("checked")}function a(){l=!0,c.trigger("set-global-stock-fields.tribe")}function r(){e("tr.ticket_advanced").hide(),e("tr.ticket_advanced_"+n()+":not(.sale_price)").show(),c.trigger("set-advanced-fields.tribe"),e(document.getElementById("tribetickets")).trigger("ticket-provider-changed.tribe")}function n(){var t=e('input[name="ticket_provider"]:checked');return t.length>0?t[0].value:""}var d=e("#tribe-event-datepickers"),c=e("#tribetickets"),o=e("#event_tickets"),s=e("#tribe-tickets-enable-global-stock"),_=e("#tribe-tickets-global-stock-level"),l=!1,k=e("html, body"),g=0;c.on({"spin.tribe":function(t,i){("undefined"==typeof i||e.inArray(i,["start","stop"]))&&(i="stop"),"stop"===i?o.css("opacity","1").find("#tribe-loading").hide():o.css("opacity","0.5").find("#tribe-loading").show()},"clear.tribe":function(){var t=e(this),i=t.find("#ticket_form"),a=i.find("tr:not(.event-wide-settings)");t.find("a#ticket_form_toggle").show(),a.find('input:not(:button):not(:radio):not(:checkbox):not([type="hidden"]), textarea').val(""),a.find("input:checkbox").attr("checked",!1),a.find("#ticket_id").val(""),t.find('#ticket_form input[name="show_attendee_info"]').prop("checked",!1).change(),t.find("input[data-default-value]").each(function(){var t=e(this);t.val(t.data("default-value"))}),t.find("#ticket_start_date").datepicker("option","maxDate",null),t.find("#ticket_end_date").datepicker("option","minDate",null),t.find(".ticket_start_time, .ticket_end_time, .ticket.sale_price").hide(),t.find("#ticket_price").removeProp("disabled").siblings(".no-update-message").html("").hide().end().siblings(".description").show(),e("#tribe-tickets-attendee-sortables").empty(),e(".tribe-tickets-attendee-saved-fields").show(),i.hide()},"focus.tribe":function(){k.animate({scrollTop:o.offset().top-50},500)},"set-advanced-fields.tribe":function(){var t=e(this),i=t.find("#ticket_form"),a=i.find("tr.ticket_advanced:not(.ticket_advanced_meta)").find("input, select, textarea"),r=i.find("#ticket_provider:checked").val();a.each(function(){var t=e(this);t.attr("name")&&t.data("name",t.attr("name")).attr({name:"",id:""}),t.closest("tr").hasClass("ticket_advanced_"+r)&&t.data("name")&&0===t.attr("name").length&&t.attr({name:t.data("name"),id:t.data("name")})}),c.trigger("set-global-stock-fields.tribe"),e("#ticket_global_stock").change(function(){c.trigger("set-global-stock-fields.tribe")})},"set-global-stock-fields.tribe":function(){var t=n(),a=e(this).find("#ticket_form").find(".ticket_advanced_"+t);if(!(a.length<1)){var r=a.filter(".stock"),d=a.filter(".global-stock-mode"),c=d.filter(".sales-cap-field"),o=e("#ticket_global_stock").val(),s=i();if(_.toggle(s),d.toggle(i()),r.toggle(!s),s)switch(o){case"global":c.hide(),r.hide();break;case"capped":c.show(),r.hide();break;case"own":c.hide(),r.show()}}}}),d.length&&(g=d.data("startofweek"));var p={dateFormat:"yy-mm-dd",showAnim:"fadeIn",changeMonth:!0,changeYear:!0,numberOfMonths:3,firstDay:g,showButtonPanel:!0,onChange:function(){},onSelect:function(t,i){var a=e.datepicker.parseDate("yy-mm-dd",t);"ticket_start_date"===i.id?(e("#ticket_end_date").datepicker("option","minDate",a),a?e(".ticket_start_time").show():e(".ticket_start_time").hide()):(e("#ticket_start_date").datepicker("option","maxDate",a),a?e(".ticket_end_time").show():e(".ticket_end_time").hide())}};e("#ticket_start_date").datepicker(p).keyup(function(t){8!==t.keyCode&&46!==t.keyCode||e.datepicker._clearDate(this)}),e("#ticket_end_date").datepicker(p).keyup(function(t){8!==t.keyCode&&46!==t.keyCode||e.datepicker._clearDate(this)}),s.change(a),s.trigger("change"),l=!1,e("input[name=ticket_provider]:radio").change(function(){r()}),e("input[name=ticket_provider]:checked").each(function(){r()}),e("a#ticket_form_toggle").click(function(t){e("h4.ticket_form_title_edit").hide(),e("h4.ticket_form_title_add").show(),e(this).hide(),c.trigger("clear.tribe").trigger("set-advanced-fields.tribe").trigger("focus.tribe"),e("#ticket_form").show(),e(document.getElementById("tribetickets")).trigger("ticket-provider-changed.tribe"),t.preventDefault()}),e("#ticket_form_cancel").click(function(){c.trigger("clear.tribe").trigger("set-advanced-fields.tribe").trigger("focus.tribe")}),e("#ticket_form_save").click(function(t){var i=e("#ticket_form_table"),a=i.find("#ticket_provider:checked").val(),r=i.find(".ticket, .ticket_advanced_meta, .ticket_advanced_"+a);c.trigger("save-ticket.tribe",t).trigger("spin.tribe","start");var n={action:"tribe-ticket-add-"+e("input[name=ticket_provider]:checked").val(),formdata:r.find(".ticket_field").serialize(),post_ID:e("#post_ID").val(),nonce:TribeTickets.add_ticket_nonce};e.post(ajaxurl,n,function(t){c.trigger("saved-ticket.tribe",t),t.success&&(c.trigger("clear.tribe"),e("td.ticket_list_container").empty().html(t.data.html),e(".ticket_time").hide())},"json").complete(function(){c.trigger("spin.tribe","stop").trigger("focus.tribe")})}),c.on("click",".ticket_delete",function(t){t.preventDefault(),c.trigger("delete-ticket.tribe",t).trigger("spin.tribe","start");var i={action:"tribe-ticket-delete-"+e(this).attr("attr-provider"),post_ID:e("#post_ID").val(),ticket_id:e(this).attr("attr-ticket-id"),nonce:TribeTickets.remove_ticket_nonce};e.post(ajaxurl,i,function(t){c.trigger("deleted-ticket.tribe",t),t.success&&(c.trigger("clear.tribe"),e("td.ticket_list_container").empty().html(t.data))},"json").complete(function(){c.trigger("spin.tribe","stop")})}),c.on("click",".ticket_edit",function(t){t.preventDefault(),e("h4.ticket_form_title_edit").show(),e("h4.ticket_form_title_add").hide(),c.trigger("spin.tribe","start");var i={action:"tribe-ticket-edit-"+e(this).attr("attr-provider"),post_ID:e("#post_ID").val(),ticket_id:e(this).attr("attr-ticket-id"),nonce:TribeTickets.edit_ticket_nonce};e.post(ajaxurl,i,function(t){c.trigger("clear.tribe").trigger("set-advanced-fields.tribe").trigger("edit-ticket.tribe",t);var i=t.data.price,a=i,r=!1;"undefined"!=typeof t.data.on_sale&&t.data.on_sale&&(r=!0,i=t.data.regular_price),e("#ticket_id").val(t.data.ID),e("#ticket_name").val(t.data.name),e("#ticket_description").val(t.data.description),r&&e(".ticket_advanced_"+t.data.provider_class+".sale_price").show();var n=t.data.start_date.substring(0,10),d=t.data.end_date.substring(0,10);e("#ticket_start_date").val(n),e("#ticket_end_date").val(d);var o=e(document.getElementById("ticket_start_meridian")),s=e(document.getElementById("ticket_end_meridian"));if(t.data.start_date){var _=parseInt(t.data.start_date.substring(11,13)),l="am";_>12&&o.length&&(l="pm",_=parseInt(_)-12,_=("0"+_).slice(-2)),12===_&&(l="pm"),0===_&&"am"===l&&(_=12),_=_.toString(),1===_.length&&(_="0"+_),e("#ticket_start_hour").val(_),e("#ticket_start_meridian").val(l),e(".ticket_start_time").show()}if(t.data.end_date){var k=parseInt(t.data.end_date.substring(11,13)),g="am";k>12&&s.length&&(g="pm",k=parseInt(k)-12,k=("0"+k).slice(-2)),12===k&&(g="pm"),0===k&&"am"===g&&(k=12),k=k.toString(),1===k.length&&(k="0"+k),e("#ticket_end_hour").val(k),e("#ticket_end_meridian").val(g),e("#ticket_start_minute").val(t.data.start_date.substring(14,16)),e("#ticket_end_minute").val(t.data.end_date.substring(14,16)),e(".ticket_end_time").show()}var p=e("tr.ticket_advanced input");p.data("name",p.attr("name")).attr({name:"",id:""}),e("tr.ticket_advanced").remove(),e("tr.ticket.bottom").before(t.data.advanced_fields),e("input:radio[name=ticket_provider]").filter("[value="+t.data.provider_class+"]").click(),e("input[name=ticket_provider]:radio").change();var m=c.find("#ticket_price");m.val(i),"undefined"!=typeof t.data.disallow_update_price_message?m.siblings(".no-update-message").html(t.data.disallow_update_price_message):m.siblings(".no-update-message").html(""),"undefined"==typeof t.data.can_update_price||t.data.can_update_price?(m.removeProp("disabled"),m.siblings(".description").show(),m.siblings(".no-update-message").hide()):(m.prop("disabled","disabled"),m.siblings(".description").hide(),m.siblings(".no-update-message").show());var f=c.find("#ticket_sale_price");r?f.val(a).closest("tr").show():f.closest("tr").hide(),"undefined"!=typeof t.data.purchase_limit&&t.data.purchase_limit&&e("#ticket_purchase_limit").val(t.data.purchase_limit),c.find(".bumpdown-trigger").bumpdown(),c.find(".bumpdown").hide(),e("a#ticket_form_toggle").hide(),e("#ticket_form").show(),c.trigger("set-advanced-fields.tribe").trigger("edit-ticket.tribe")},"json").complete(function(){c.trigger("spin.tribe","stop").trigger("focus.tribe")})}).on("click","#tribe_ticket_header_image",function(t){t.preventDefault(),ticketHeaderImage.uploader("","")});var m=e("#tribe_ticket_header_remove"),f=e("#tribe_ticket_header_preview");if(f.find("img").length&&m.show(),_.change(function(){l=!0}),e('input[type="submit"]').click(function(){l=!1}),e(t).on("beforeunload",function(){return l?tribe_global_stock_admin_ui.nav_away_msg:void 0}),e("body").on("click","#tribe_ticket_header_remove",function(t){t.preventDefault(),f.html(""),m.hide(),e("#tribe_ticket_header_image_id").val("")}),e("#tribe_ticket_header_preview img").length){var h=e("#tribe_ticket_header_preview img");h.removeAttr("width").removeAttr("height"),c.width()<h.width()&&h.css("width","95%")}})}(window,jQuery);
src/template-tags/tickets.php CHANGED
@@ -194,24 +194,53 @@ if ( ! function_exists( 'tribe_tickets_get_ticket_stock_message' ) ) {
194
  /**
195
  * Gets the "tickets sold" message for a given ticket
196
  *
197
- * @param Tribe__Ticket_Object $ticket Ticket to analyze
198
  *
199
  * @return string
200
  */
201
- function tribe_tickets_get_ticket_stock_message( $ticket ) {
202
- $stock = $ticket->original_stock();
203
- $sold = $ticket->qty_sold();
204
- $pending = $ticket->qty_pending();
205
-
206
- $pending_message = '';
207
- if ( $pending > 0 ) {
208
- $pending_message = sprintf( _n( '(%d awaiting review)', '(%d awaiting review)', $pending, 'event-tickets' ), (int) $pending );
 
 
 
 
 
 
 
209
  }
210
 
211
- if ( ! $stock ) {
212
- $message = sprintf( esc_html__( 'Sold all %1$d %2$s', 'event-tickets' ), esc_html( $sold ), esc_html( $pending_message ) );
213
- } else {
214
- $message = sprintf( esc_html__( 'Sold %1$d of %2$d %3$s', 'event-tickets' ), esc_html( $sold ), esc_html( $stock ), esc_html( $pending_message ) );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
215
  }
216
 
217
  return $message;
194
  /**
195
  * Gets the "tickets sold" message for a given ticket
196
  *
197
+ * @param Tribe__Tickets__Ticket_Object $ticket Ticket to analyze
198
  *
199
  * @return string
200
  */
201
+ function tribe_tickets_get_ticket_stock_message( Tribe__Tickets__Ticket_Object $ticket ) {
202
+ $stock = $ticket->stock();
203
+ $sold = $ticket->qty_sold();
204
+ $cancelled = $ticket->qty_cancelled();
205
+ $pending = $ticket->qty_pending();
206
+ $event = Tribe__Tickets__Tickets::find_matching_event( $ticket );
207
+ $global_stock = new Tribe__Tickets__Global_Stock( $event->ID );
208
+
209
+ $is_global = Tribe__Tickets__Global_Stock::GLOBAL_STOCK_MODE === $ticket->global_stock_mode();
210
+ $is_capped = Tribe__Tickets__Global_Stock::CAPPED_STOCK_MODE === $ticket->global_stock_mode();
211
+ $stock_cap = $ticket->global_stock_cap();
212
+
213
+ // If ticket sales are capped, do not suggest that more than the cap amount are available
214
+ if ( $is_capped && $stock > $stock_cap ) {
215
+ $stock = $stock_cap;
216
  }
217
 
218
+ // If it is a global-stock ticket but the global stock level has not yet been set for the event
219
+ // then return something better than just '0' as the available stock
220
+ if ( $is_global && 0 === $stock && ! $global_stock->is_enabled() ) {
221
+ $stock = '<i>' . __( 'global inventory', 'event-tickets-plus' ) . '</i>';
222
+ }
223
+
224
+ // There may not be a fixed inventory - in which case just report the number actually sold so far
225
+ if ( empty( $stock ) && $stock !== 0 ) {
226
+ $message = sprintf( esc_html__( 'Sold %d', 'event-tickets' ), esc_html( $sold ) );
227
+ }
228
+ // If we do have a fixed stock then we can provide more information
229
+ else {
230
+ $cancelled_count = empty( $cancelled ) ? '' : esc_html( sprintf(
231
+ _x( ' cancelled: %1$d', 'ticket stock message (cancelled stock)', 'event-tickets' ),
232
+ (int) $cancelled
233
+ ) );
234
+
235
+ $pending_count = $pending < 1 ? '' : esc_html( sprintf(
236
+ __( ' pending: %1$d', 'ticket stock message (pending stock)', 'event-tickets' ),
237
+ (int) $pending
238
+ ) );
239
+
240
+ $message = sprintf(
241
+ esc_html__( 'Sold %1$d (units remaining: %2$s%3$s%4$s)', 'event-tickets' ),
242
+ esc_html( $sold ), $stock, $cancelled_count, $pending_count
243
+ );
244
  }
245
 
246
  return $message;
src/views/tickets/rsvp.php CHANGED
@@ -1,19 +1,23 @@
1
  <?php
 
 
 
 
 
 
2
 
3
  $is_there_any_product = false;
4
  $is_there_any_product_to_sell = false;
5
 
6
  ob_start();
 
 
7
  ?>
8
- <form action="" class="cart" method="post" enctype='multipart/form-data'>
9
  <h2 class="tribe-events-tickets-title"><?php esc_html_e( 'RSVP', 'event-tickets' ) ?></h2>
10
- <?php
11
- $messages = Tribe__Tickets__RSVP::get_instance()->get_messages();
12
-
13
- if ( $messages ) {
14
- ?>
15
- <div class="tribe-rsvp-messages">
16
- <?php
17
  foreach ( $messages as $message ) {
18
  ?>
19
  <div class="tribe-rsvp-message tribe-rsvp-message-<?php echo esc_attr( $message->type ); ?>">
@@ -21,11 +25,12 @@ ob_start();
21
  </div>
22
  <?php
23
  }//end foreach
24
- ?>
 
 
 
25
  </div>
26
- <?php
27
- }//end if
28
- ?>
29
  <table width="100%" class="tribe-events-tickets tribe-events-tickets-rsvp">
30
  <?php
31
  foreach ( $tickets as $ticket ) {
@@ -39,7 +44,7 @@ ob_start();
39
 
40
  ?>
41
  <tr>
42
- <td class="tribe-ticket">
43
  <input type="hidden" name="product_id[]" value="<?php echo absint( $ticket->ID ); ?>">
44
  <?php
45
  if ( $ticket->is_in_stock() ) {
@@ -73,6 +78,14 @@ ob_start();
73
  </td>
74
  </tr>
75
  <?php
 
 
 
 
 
 
 
 
76
  }
77
  }//end foreach
78
 
@@ -80,10 +93,19 @@ ob_start();
80
  ?>
81
  <tr class="tribe-tickets-meta-row">
82
  <td colspan="4" class="tribe-tickets-attendees">
 
 
 
 
 
 
 
 
 
83
  <table>
84
  <tr class="tribe-tickets-full-name-row">
85
  <td>
86
- <label for="tribe-tickets-full-name"><?php esc_html_e( 'Full Name:', 'event-tickets' ); ?></label>
87
  </td>
88
  <td colspan="3">
89
  <input type="text" name="attendee[full_name]" id="tribe-tickets-full-name">
@@ -91,12 +113,18 @@ ob_start();
91
  </tr>
92
  <tr class="tribe-tickets-email-row">
93
  <td>
94
- <label for="tribe-tickets-email"><?php esc_html_e( 'Email:', 'event-tickets' ); ?></label>
95
  </td>
96
  <td colspan="3">
97
  <input type="email" name="attendee[email]" id="tribe-tickets-email">
98
  </td>
99
  </tr>
 
 
 
 
 
 
100
  </table>
101
  </td>
102
  </tr>
1
  <?php
2
+ /**
3
+ * This template renders the RSVP ticket form
4
+ *
5
+ * @version 4.1
6
+ *
7
+ */
8
 
9
  $is_there_any_product = false;
10
  $is_there_any_product_to_sell = false;
11
 
12
  ob_start();
13
+ $messages = Tribe__Tickets__RSVP::get_instance()->get_messages();
14
+ $messages_class = $messages ? 'tribe-rsvp-message-display' : '';
15
  ?>
16
+ <form action="" class="cart <?php echo esc_attr( $messages_class ); ?>" method="post" enctype='multipart/form-data'>
17
  <h2 class="tribe-events-tickets-title"><?php esc_html_e( 'RSVP', 'event-tickets' ) ?></h2>
18
+ <div class="tribe-rsvp-messages">
19
+ <?php
20
+ if ( $messages ) {
 
 
 
 
21
  foreach ( $messages as $message ) {
22
  ?>
23
  <div class="tribe-rsvp-message tribe-rsvp-message-<?php echo esc_attr( $message->type ); ?>">
25
  </div>
26
  <?php
27
  }//end foreach
28
+ }//end if
29
+ ?>
30
+ <div class="tribe-rsvp-message tribe-rsvp-message-error tribe-rsvp-message-confirmation-error" style="display:none;">
31
+ <?php echo esc_html_e( 'Please fill in the RSVP confirmation name and email fields.', 'event-tickets' ); ?>
32
  </div>
33
+ </div>
 
 
34
  <table width="100%" class="tribe-events-tickets tribe-events-tickets-rsvp">
35
  <?php
36
  foreach ( $tickets as $ticket ) {
44
 
45
  ?>
46
  <tr>
47
+ <td class="tribe-ticket quantity" data-product-id="<?php echo esc_attr( $ticket->ID ); ?>">
48
  <input type="hidden" name="product_id[]" value="<?php echo absint( $ticket->ID ); ?>">
49
  <?php
50
  if ( $ticket->is_in_stock() ) {
78
  </td>
79
  </tr>
80
  <?php
81
+
82
+ /**
83
+ * Allows injection of HTML after an RSVP ticket table row
84
+ *
85
+ * @var Event ID
86
+ * @var Tribe__Tickets__Ticket_Object
87
+ */
88
+ do_action( 'event_tickets_rsvp_after_ticket_row', tribe_events_get_ticket_event( $ticket->id ), $ticket );
89
  }
90
  }//end foreach
91
 
93
  ?>
94
  <tr class="tribe-tickets-meta-row">
95
  <td colspan="4" class="tribe-tickets-attendees">
96
+ <header><?php esc_html_e( 'Send RSVP confirmation to:', 'event-tickets-plus' ); ?></header>
97
+ <?php
98
+ /**
99
+ * Allows injection of HTML before RSVP ticket confirmation fields
100
+ *
101
+ * @var array of Tribe__Tickets__Ticket_Object
102
+ */
103
+ do_action( 'event_tickets_rsvp_before_confirmation_fields', $tickets );
104
+ ?>
105
  <table>
106
  <tr class="tribe-tickets-full-name-row">
107
  <td>
108
+ <label for="tribe-tickets-full-name"><?php esc_html_e( 'Full Name', 'event-tickets' ); ?>:</label>
109
  </td>
110
  <td colspan="3">
111
  <input type="text" name="attendee[full_name]" id="tribe-tickets-full-name">
113
  </tr>
114
  <tr class="tribe-tickets-email-row">
115
  <td>
116
+ <label for="tribe-tickets-email"><?php esc_html_e( 'Email', 'event-tickets' ); ?>:</label>
117
  </td>
118
  <td colspan="3">
119
  <input type="email" name="attendee[email]" id="tribe-tickets-email">
120
  </td>
121
  </tr>
122
+ <tr class="tribe-tickets-attendees-list-optout">
123
+ <td colspan="4">
124
+ <input type="checkbox" name="attendee[optout]" id="tribe-tickets-attendees-list-optout">
125
+ <label for="tribe-tickets-attendees-list-optout"><?php esc_html_e( 'Don\'t list me on the public attendee list', 'event-tickets' ); ?></label>
126
+ </td>
127
+ </tr>
128
  </table>
129
  </td>
130
  </tr>
tests.md DELETED
@@ -1,47 +0,0 @@
1
- # Events Calendar PRO tests
2
-
3
- This is a brief and quick guide that's covering the bare essentials needed to set up the tests on your local plugin copy.
4
- Please refer to [Codeception](http://codeception.com/docs) and [WP Browser](https://github.com/lucatume/wp-browser) documentation for any issue that's not TEC related.
5
-
6
- ## Set up
7
- After cloning the TEX repository on your local machine change directory to the plugin root folder and pull in any needed dependency using [Composer](https://getcomposer.org/):
8
-
9
- composer update
10
-
11
- when Composer finished the update process (might take a while) set up your own [Codeception](http://codeception.com/) installation running
12
-
13
- vendor/bin/wpcept bootstrap
14
-
15
- The `wpcept bootstrap` command is a modified version of the default `codecept bootstrap` command that will take care of setting up a WordPress-friendly testing environment.
16
- To be able to run successfully on your system Codeception will need to be configured to look for the right database, the right WordPress installation and so on.
17
- Codeception allows for "distribution" versions of its configuration to be shared among developers, what you define in your local Codeception configuration files will override the "distribution" setting; think of CSS rules.
18
- The repository contains a `codeception.dist.yml` file that Codeception will read before reading the local to your machine `codeception.yml` file.
19
- Copy the distribution version of the Codeception configuration file in the root folder of the plugin
20
-
21
- cp codeception.dist.yml codeception.yml
22
-
23
- **Edit the file `codeception.yml` file to suit your database, installation folder and web driver settings.**
24
-
25
- **Beware**: The `WPLoader` module that's used in functional tests will **destroy** the database it's working on: **do not** point it to the same database you use for development! A good rule of thumb is to have a database for development (e.g. `tec`) and one that will be used for tests (e.g. `tec-tests`).
26
- On the same lines the repository packs "distribution" versions of the `unit.suite.dist.yml`, `functional.suite.dist.yml` and `acceptance.suite.dist.yml` configuration files: there is usually no need to override those but it's worth mentioning they exist.
27
- The last piece of the configuration is the bootstrap file; the repository comes with "distribution" versions of these file in the root folder of the pluging tests (`/tests/_bootstrap.dist.php`) and a bootstrap file specific to each suite (`/tests/acceptance/_bootstrap.dist.php`, `/tests/functional/_bootstrap.dist.php`, `/tests/unit/_bootstrap.dist.php`); remove the root `_bootstrap.php` file Codeception created during bootstrapping and copy the one in the root of the plugin tests (`/tests`)
28
-
29
- rm _bootstrap.php
30
- cp _bootstrap.dist.php _bootstrap.php
31
-
32
- You *should* not need to edit anything in any bootstrap file to make things work. Do the same for the suite specific bootstrap files
33
-
34
- cp acceptance/_bootstrap.dist.php acceptance/_bootstrap.php
35
- cp functional/_bootstrap.dist.php functional/_bootstrap.php
36
- cp unit/_bootstrap.dist.php unit/_bootstrap.php
37
-
38
- ## Running the tests
39
- Nothing different from a default Codeception environment so this command will run all the tests
40
-
41
- vendor/bin/codecept run
42
-
43
- Failing tests are ok in set up terms: the system works. Errors should be reported.
44
- Please refer to [Codeception documentation](http://codeception.com/docs) to learn about more run and configuaration options.
45
-
46
- ## Contributing to tests
47
- Should you come up with good utility methods, worthy database configurations and "cool additions" in general for the plugin tests feel free to open a PR and submit them for review.