├── .gitignore
├── images
├── icons.png
├── jquery-ui-icons_21759b.png
├── jquery-ui-icons_333333.png
├── jquery-ui-icons_999999.png
└── jquery-ui-icons_cc0000.png
├── screenshot-1.png
├── screenshot-2.png
├── screenshot-3.png
├── screenshot-4.png
├── screenshot-5.png
├── screenshot-6.png
├── screenshot-7.png
├── languages
├── camptix-uk.mo
├── camptix-da_DK.mo
├── camptix-de_DE.mo
├── camptix-he_IL.mo
├── camptix-pl_PL.mo
└── camptix-ru_RU.mo
├── phpunit.xml.dist
├── inc
├── class-camptix-addon.php
├── class-wp-cli-commands.php
├── class-camptix-payment-method.php
└── class-camptix-currencies.php
├── tests
├── bootstrap.php
└── test-camptix.php
├── views
├── addons
│ └── field-tshirt-report.php
└── payment-options.php
├── addons
├── logging-file.php
├── logging-file-json.php
├── field-url.php
├── logging-meta.php
├── field-twitter.php
├── field-tshirt.php
├── field-country.php
├── track-attendance.php
├── privacy.php
├── shortcodes.php
└── payment-stripe.php
├── camptix.css
├── readme.txt
├── admin.css
├── help.php
├── camptix.js
└── admin.js
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | .svn
3 | wiki/
4 |
--------------------------------------------------------------------------------
/images/icons.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Automattic/camptix/HEAD/images/icons.png
--------------------------------------------------------------------------------
/screenshot-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Automattic/camptix/HEAD/screenshot-1.png
--------------------------------------------------------------------------------
/screenshot-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Automattic/camptix/HEAD/screenshot-2.png
--------------------------------------------------------------------------------
/screenshot-3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Automattic/camptix/HEAD/screenshot-3.png
--------------------------------------------------------------------------------
/screenshot-4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Automattic/camptix/HEAD/screenshot-4.png
--------------------------------------------------------------------------------
/screenshot-5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Automattic/camptix/HEAD/screenshot-5.png
--------------------------------------------------------------------------------
/screenshot-6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Automattic/camptix/HEAD/screenshot-6.png
--------------------------------------------------------------------------------
/screenshot-7.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Automattic/camptix/HEAD/screenshot-7.png
--------------------------------------------------------------------------------
/languages/camptix-uk.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Automattic/camptix/HEAD/languages/camptix-uk.mo
--------------------------------------------------------------------------------
/languages/camptix-da_DK.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Automattic/camptix/HEAD/languages/camptix-da_DK.mo
--------------------------------------------------------------------------------
/languages/camptix-de_DE.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Automattic/camptix/HEAD/languages/camptix-de_DE.mo
--------------------------------------------------------------------------------
/languages/camptix-he_IL.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Automattic/camptix/HEAD/languages/camptix-he_IL.mo
--------------------------------------------------------------------------------
/languages/camptix-pl_PL.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Automattic/camptix/HEAD/languages/camptix-pl_PL.mo
--------------------------------------------------------------------------------
/languages/camptix-ru_RU.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Automattic/camptix/HEAD/languages/camptix-ru_RU.mo
--------------------------------------------------------------------------------
/images/jquery-ui-icons_21759b.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Automattic/camptix/HEAD/images/jquery-ui-icons_21759b.png
--------------------------------------------------------------------------------
/images/jquery-ui-icons_333333.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Automattic/camptix/HEAD/images/jquery-ui-icons_333333.png
--------------------------------------------------------------------------------
/images/jquery-ui-icons_999999.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Automattic/camptix/HEAD/images/jquery-ui-icons_999999.png
--------------------------------------------------------------------------------
/images/jquery-ui-icons_cc0000.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Automattic/camptix/HEAD/images/jquery-ui-icons_cc0000.png
--------------------------------------------------------------------------------
/phpunit.xml.dist:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
12 | ./tests/
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/inc/class-camptix-addon.php:
--------------------------------------------------------------------------------
1 | register_addon( $class_name );
34 | }
35 |
--------------------------------------------------------------------------------
/tests/bootstrap.php:
--------------------------------------------------------------------------------
1 | get_options();
14 |
15 | if ( apply_filters( 'camptix_enable_automatic_upgrades', true ) ) {
16 | WP_CLI::warning( "The 'camptix_enable_automatic_upgrades' filter is set to true. You probably want it set to false; otherwise the upgrade will probably run automatically before you get a chance to run it manually." );
17 | }
18 |
19 | if ( $options['version'] < $camptix->version ) {
20 | $camptix->log( 'Running manual upgrade.', 0, null, 'upgrade' );
21 |
22 | if ( $camptix->upgrade( $options['version'] ) ) {
23 | WP_CLI::success( "The upgrade routine has finished running. Upgraded from {$options['version']} to {$camptix->version}." );
24 | } else {
25 | WP_CLI::error( 'The upgrade routine was not able to run. The most likely cause is that another upgrade routine is already in progress.' );
26 | }
27 | } else {
28 | WP_CLI::warning( 'CampTix does not need to upgrade. The upgrade routine was not run.' );
29 | }
30 | }
31 | }
32 |
33 | WP_CLI::add_command( 'camptix', 'CampTix_Command' );
--------------------------------------------------------------------------------
/views/addons/field-tshirt-report.php:
--------------------------------------------------------------------------------
1 |
16 |
17 | $site ) : ?>
18 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 | $size_count ) : ?>
47 |
48 |
49 |
50 |
51 |
52 | %
53 |
54 |
55 |
56 |
57 |
58 |
--------------------------------------------------------------------------------
/addons/logging-file.php:
--------------------------------------------------------------------------------
1 | _logfile )
23 | $this->_logfile = fopen( apply_filters( 'camptix_logfile_path', '/tmp/camptix.log' ), 'a+' );
24 |
25 | // If there was an error opening the log file, don't do anything else.
26 | if ( ! $this->_logfile )
27 | return;
28 |
29 | $url = parse_url( home_url() );
30 | $message = sprintf( '[%s] %s/%s: %s', date( 'Y-m-d H:i:s' ), $url['host'], $section . ( $post_id ? '/' . $post_id : '' ), $message );
31 | fwrite( $this->_logfile, $message . PHP_EOL );
32 | }
33 |
34 | function __destruct() {
35 | if ( $this->_logfile )
36 | fclose( $this->_logfile );
37 | }
38 | }
39 |
40 | // Register this class as a CampTix Addon.
41 | camptix_register_addon( 'CampTix_Addon_Logging_File' );
--------------------------------------------------------------------------------
/views/payment-options.php:
--------------------------------------------------------------------------------
1 |
15 |
16 |
17 | 0 ) : ?>
18 |
19 | $payment_method ) : ?>
20 |
26 | value="">
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 | _logfile )
23 | $this->_logfile = fopen( apply_filters( 'camptix_logfile_json_path', '/tmp/camptix.json.log' ), 'a+' );
24 |
25 | // If there was an error opening the log file, don't do anything else.
26 | if ( ! $this->_logfile )
27 | return;
28 |
29 | $entry = array(
30 | 'url' => home_url(),
31 | 'timestamp' => time(),
32 | 'message' => $message,
33 | 'data' => stripslashes_deep( $data ),
34 | 'module' => $section,
35 | );
36 |
37 | if ( $post_id ) {
38 | $entry['post_id'] = $post_id;
39 | $entry['edit_post_link'] = esc_url_raw( add_query_arg( array( 'post' => $post_id, 'action' => 'edit' ), admin_url( 'post.php' ) ) );
40 | }
41 |
42 | fwrite( $this->_logfile, json_encode( $entry ) . PHP_EOL );
43 | }
44 |
45 | function __destruct() {
46 | if ( $this->_logfile )
47 | fclose( $this->_logfile );
48 | }
49 | }
50 |
51 | // Register this class as a CampTix Addon.
52 | camptix_register_addon( 'CampTix_Addon_Logging_File_JSON' );
--------------------------------------------------------------------------------
/tests/test-camptix.php:
--------------------------------------------------------------------------------
1 | assertEquals( $expected_output, CampTix_Plugin::esc_csv( $test_input ) );
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/addons/field-url.php:
--------------------------------------------------------------------------------
1 | 'URL (public)',
23 | ) );
24 | }
25 |
26 | /**
27 | * A url input for a question.
28 | */
29 | function question_field_url( $name, $value, $question, $required = false ) {
30 | ?>
31 | />
32 | questions = $camptix->get_all_questions();
38 | }
39 |
40 | function attendees_shortcode_item( $attendee_id ) {
41 | foreach ( $this->questions as $question ) {
42 | if ( get_post_meta( $question->ID, 'tix_type', true ) != 'url' ) {
43 | continue;
44 | }
45 |
46 | $answers = (array) get_post_meta( $attendee_id, 'tix_questions', true );
47 |
48 | if ( ! isset( $answers[ $question->ID ] ) ) {
49 | continue;
50 | }
51 |
52 | $url = esc_url_raw( trim( $answers[ $question->ID ] ) );
53 |
54 | if ( $url ) {
55 | $parsed = wp_parse_url( $url );
56 |
57 | if ( empty( $parsed['host'] ) ) {
58 | continue;
59 | }
60 |
61 | $label = $parsed['host'];
62 |
63 | if ( isset( $parsed['path'] ) ) {
64 | $label .= untrailingslashit( $parsed['path'] );
65 | }
66 |
67 | if ( substr( $label, 0, 4 ) == 'www.' ) {
68 | $label = substr( $label, 4 );
69 | }
70 |
71 | printf( '%s ', esc_url( $url ), esc_html( $label ) );
72 | }
73 | }
74 | }
75 | }
76 |
77 | // Register this class as a CampTix Addon.
78 | camptix_register_addon( 'CampTix_Addon_URL_Field' );
79 |
--------------------------------------------------------------------------------
/addons/logging-meta.php:
--------------------------------------------------------------------------------
1 | home_url(),
23 | 'timestamp' => time(),
24 | 'message' => $message,
25 | 'data' => $data,
26 | 'module' => $section,
27 | );
28 |
29 | if ( $post_id ) {
30 | $entry['post_id'] = $post_id;
31 | $entry['edit_post_link'] = esc_url_raw( add_query_arg( array( 'post' => $post_id, 'action' => 'edit' ), admin_url( 'post.php' ) ) );
32 | $log = get_post_meta( $post_id, 'tix_log', true );
33 | if ( is_array( $log ) )
34 | $log[] = $entry;
35 | else
36 | $log = array( $entry );
37 |
38 | update_post_meta( $post_id, 'tix_log', $log );
39 | }
40 | }
41 |
42 | function camptix_add_meta_boxes() {
43 | $post_types = array(
44 | 'tix_attendee',
45 | 'tix_ticket',
46 | 'tix_coupon',
47 | 'tix_email',
48 | );
49 |
50 | foreach ( $post_types as $post_type )
51 | add_meta_box( 'tix_log', 'CampTix Meta Log', array( $this, 'metabox_log' ), $post_type, 'normal' );
52 | }
53 |
54 | /**
55 | * CampTix Log metabox for various post types.
56 | */
57 | function metabox_log() {
58 | global $post, $camptix;
59 | $rows = array();
60 |
61 | // The log is stored in an array of array( 'timestamp' => x, 'message' => y ) format.
62 | $log = get_post_meta( $post->ID, 'tix_log', true );
63 | if ( !$log ) $log = array();
64 |
65 | // Add entries as rows.
66 | foreach ( $log as $entry )
67 | $rows[] = array( date( 'Y-m-d H:i:s', intval( $entry['timestamp'] ) ), esc_html( $entry['message'] ) );
68 |
69 | if ( count( $rows ) < 1 )
70 | $rows[] = array( 'No log entries yet.', '' );
71 |
72 | $camptix->table( $rows, 'tix-log-table' );
73 | }
74 | }
75 |
76 | // Register this class as a CampTix Addon.
77 | camptix_register_addon( 'CampTix_Addon_Logging_Meta' );
--------------------------------------------------------------------------------
/addons/field-twitter.php:
--------------------------------------------------------------------------------
1 | 'Twitter (public)',
27 | ) );
28 | }
29 |
30 | function attendees_shortcode_init() {
31 | global $camptix;
32 | $this->questions = $camptix->get_all_questions();
33 | }
34 |
35 | function attendees_shortcode_item( $attendee_id ) {
36 | foreach ( $this->questions as $question ) {
37 | if ( get_post_meta( $question->ID, 'tix_type', true ) != 'twitter' )
38 | continue;
39 |
40 | $answers = (array) get_post_meta( $attendee_id, 'tix_questions', true );
41 | if ( ! isset( $answers[ $question->ID ] ) )
42 | continue;
43 |
44 | $value = trim( $answers[ $question->ID ] );
45 | $matches = array();
46 | $screen_name = false;
47 |
48 | // We allow "username", "@username" and "http://twitter.com/username" values.
49 | if ( preg_match( '#^@?([a-z0-9_]+)$#i', $value, $matches ) )
50 | $screen_name = $matches[1];
51 | elseif ( preg_match( '#^(https?://)?(www\.)?twitter\.com/(\#!/)?([a-z0-9]+)$#i', $value, $matches ) )
52 | $screen_name = $matches[4];
53 |
54 | if ( $screen_name ) {
55 | $url = 'http://twitter.com/' . $screen_name;
56 | printf( '', esc_url( $url ), esc_html( $screen_name ) );
57 | }
58 | }
59 | }
60 | }
61 |
62 | // Register this addon, creates an instance of this class when necessary.
63 | camptix_register_addon( 'CampTix_Addon_Twitter_Field' );
--------------------------------------------------------------------------------
/addons/field-tshirt.php:
--------------------------------------------------------------------------------
1 | 'T-Shirt Size (public)',
27 | ) );
28 | }
29 |
30 | function question_field_tshirt( $name, $value, $question, $required = false ) {
31 | $values = get_post_meta( $question->ID, 'tix_values', true );
32 | ?>
33 | >
34 |
35 | value="">
36 |
37 |
38 | 'ids',
67 | 'number' => 200,
68 | 'orderby' => 'id',
69 | 'order' => 'DESC',
70 | ) );
71 |
72 | foreach ( $sites as $site_id ) {
73 | switch_to_blog( $site_id );
74 |
75 | $sizes = $this->get_aggregated_sizes();
76 |
77 | if ( ! empty( $sizes ) ) {
78 | $sizes_by_site[ $site_id ]['name'] = get_wordcamp_name();
79 | $sizes_by_site[ $site_id ]['message'] = apply_filters( 'camptix_tshirt_report_intro', '', $site_id, $sizes );
80 | $sizes_by_site[ $site_id ]['sizes'] = $sizes;
81 | }
82 |
83 | restore_current_blog();
84 | }
85 |
86 | update_site_option( 'tix_aggregated_tshirt_sizes', $sizes_by_site );
87 | }
88 |
89 | /**
90 | * Get the counts for each shirt size that attendees selected
91 | *
92 | * @return array
93 | */
94 | protected function get_aggregated_sizes() {
95 | $size_counts = array();
96 |
97 | $questions = get_posts( array(
98 | 'post_type' => 'tix_question',
99 | 'posts_per_page' => 1000,
100 | 'meta_key' => 'tix_type',
101 | 'meta_value' => 'tshirt',
102 | ) );
103 |
104 | if ( empty( $questions ) ) {
105 | return $size_counts;
106 | }
107 |
108 | $attendees = get_posts( array(
109 | 'post_type' => 'tix_attendee',
110 | 'posts_per_page' => 10000,
111 | ) );
112 |
113 | foreach ( $questions as $question ) {
114 | foreach ( $attendees as $attendee ) {
115 | if ( empty( $attendee->tix_questions[ $question->ID ] ) ) {
116 | continue;
117 | }
118 |
119 | $size = $attendee->tix_questions[ $question->ID ];
120 |
121 | if ( empty( $size_counts[ $size ] ) ) {
122 | $size_counts[ $size ] = 0;
123 | }
124 |
125 | $size_counts[ $size ]++;
126 | }
127 | }
128 |
129 | return $size_counts;
130 | }
131 | }
132 |
133 | // Register this class as a CampTix Addon.
134 | camptix_register_addon( 'CampTix_Addon_Tshirt_Field' );
135 |
--------------------------------------------------------------------------------
/camptix.css:
--------------------------------------------------------------------------------
1 | #tix-errors, #tix-notices {
2 | margin-bottom: 20px;
3 | }
4 |
5 | .tix-error, .tix-notice, .tix-info {
6 | margin: 2px 0;
7 | padding: 4px 8px;
8 | -webkit-border-radius: 3px;
9 | -moz-border-radius: 3px;
10 | border-radius: 3px;
11 | }
12 |
13 | .tix-error p,
14 | .tix-notice p,
15 | .tix-info p {
16 | margin: 0 0 1em 0;
17 | padding: 0;
18 | }
19 |
20 | .tix-error p:last-child,
21 | .tix-notice p:last-child,
22 | .tix-info p:last-child {
23 | margin-bottom: 0;
24 | }
25 |
26 | .tix-error {
27 | background: #FFEBE8;
28 | border: solid 1px #C00;
29 | }
30 |
31 | .tix-notice {
32 | background: lightYellow;
33 | border: solid 1px #E6DB55;
34 | }
35 |
36 | .tix-info {
37 | background: #EFF8DF;
38 | border: solid 1px #B2D37D;
39 | }
40 |
41 | .tix-clear {
42 | height: 0;
43 | line-height: 0;
44 | display: block;
45 | font-size: 0;
46 | clear: both;
47 | }
48 |
49 | .tix-required-star {
50 | color: #DD0000;
51 | }
52 |
53 | #tix {
54 | padding-top: 20px;
55 | }
56 | body.admin-bar #tix {
57 | padding-top: 50px;
58 | }
59 |
60 | #tix-attendees ul {
61 | list-style: none;
62 | margin-left: 0;
63 | }
64 |
65 | #tix-attendees li {
66 | float: left;
67 | width: 50%;
68 | margin-bottom: 20px;
69 | height: 80px;
70 | }
71 |
72 | #tix-attendees .tix-columns-1 li { width: 100%; }
73 | #tix-attendees .tix-columns-2 li { width: 50%; }
74 | #tix-attendees .tix-columns-3 li { width: 33.333%; }
75 | #tix-attendees .tix-columns-4 li { width: 25%; }
76 | #tix-attendees .tix-columns-5 li { width: 20%; }
77 |
78 | #tix-attendees .avatar {
79 | float: left;
80 | width: 50px;
81 | height: 50px;
82 | }
83 |
84 | #tix-attendees .tix-field {
85 | display: block;
86 | margin-left: 70px;
87 | font-size: 14px;
88 | /*width: 100%;*/
89 | }
90 |
91 | #tix-attendees .tix-attendee-name {
92 | margin-bottom: 0;
93 | clear: none;
94 | }
95 |
96 | .entry-content input,
97 | .entry-content select {
98 | margin: 0 0 0 0;
99 | }
100 |
101 | .tix-ticket-form td,
102 | .tix-attendee-form td,
103 | .tix-private-form td,
104 | .tix-receipt-form td {
105 | vertical-align: top;
106 | }
107 |
108 | /* IE tends to center-align th */
109 | .tix-ticket-form th,
110 | .tix-attendee-form th,
111 | .tix-private-form th,
112 | .tix-receipt-form th {
113 | text-align: left;
114 | }
115 |
116 | .tix-ticket-form input[type="text"],
117 | .tix-ticket-form input[type="email"],
118 | .tix-ticket-form input[type="url"],
119 | .tix-ticket-form textarea,
120 | .tix-private-form input[type="text"],
121 | .tix-private-form input[type="email"],
122 | .tix-private-form input[type="url"],
123 | .tix-private-form textarea,
124 | .tix-attendee-form input[type="text"],
125 | .tix-attendee-form input[type="email"],
126 | .tix-attendee-form input[type="url"],
127 | .tix-attendee-form textarea,
128 | .tix-receipt-form input[type="text"],
129 | .tix-receipt-form input[type="email"],
130 | .tix-receipt-form input[type="url"],
131 | .tix-receipt-form textarea {
132 | width: 70%;
133 | }
134 |
135 | .tix-ticket-form td.tix-left,
136 | .tix-private-form td.tix-left,
137 | .tix-attendee-form td.tix-left,
138 | .tix-receipt-form td.tix-left {
139 | width: 40%;
140 | }
141 | .tix-ticket-form td.tix-right,
142 | .tix-private-form td.tix-right,
143 | .tix-attendee-form td.tix-right,
144 | .tix-receipt-form td.tix-right {
145 | width: 60%;
146 | }
147 |
148 | .tix-order-summary .tix-column-description {
149 | width: 40%;
150 | }
151 |
152 | .tix-order-summary td.tix-column-per-ticket,
153 | .tix-order-summary td.tix-column-quantity,
154 | .tix-order-summary td.tix-column-price {
155 | vertical-align: middle;
156 | }
157 |
158 | .tix-submit {
159 | text-align: right;
160 | }
161 |
162 | .tix-submit input[type="submit"] {
163 | cursor: pointer;
164 | }
165 |
166 | #tix.tix-js .tix-hide-if-js,
167 | #tix .tix-show-if-js,
168 | .tix-hidden {
169 | display: none !important;
170 | }
171 | #tix.tix-js .tix-show-if-js,
172 | #tix .tix-hide-if-js {
173 | display: block;
174 | }
175 |
176 | .tix-submit select[name=tix_payment_method] {
177 | margin-right: 10px;
178 | }
179 |
180 | /**
181 | * === RTL Styles ===
182 | * Should always be at the EOF, since they need to override
183 | * some of the default styles in RTL environments.
184 | * RTL installs should have a class appended to
185 | * named 'rtl'. We're going to use it to line things up.
186 | */
187 | .rtl .tix-ticket-form th,
188 | .rtl .tix-private-form th,
189 | .rtl .tix-attendee-form th,
190 | .rtl .tix-receipt-form th { text-align: right; }
191 |
192 | .rtl #tix-attendees li,
193 | .rtl #tix-attendees .avatar { float: right; }
194 |
195 | .rtl #tix-attendees .tix-field { margin-left: 0; margin-right: 70px; }
--------------------------------------------------------------------------------
/addons/field-country.php:
--------------------------------------------------------------------------------
1 | 'Country',
25 | ) );
26 | }
27 |
28 | /**
29 | * Render the Country `select` field on the front-end.
30 | */
31 | function question_field_country( $name, $user_value, $question, $required = false ) {
32 | $countries = array( 'Abkhazia', 'Afghanistan', 'Aland', 'Albania', 'Algeria', 'American Samoa', 'Andorra', 'Angola', 'Anguilla', 'Antigua and Barbuda', 'Argentina', 'Armenia', 'Aruba', 'Ascension', 'Ashmore and Cartier Islands', 'Australia', 'Australian Antarctic Territory', 'Austria', 'Azerbaijan', 'Bahamas, The', 'Bahrain', 'Baker Island', 'Bangladesh', 'Barbados', 'Belarus', 'Belgium', 'Belize', 'Benin', 'Bermuda', 'Bhutan', 'Bolivia', 'Bosnia and Herzegovina', 'Botswana', 'Bouvet Island', 'Brazil', 'British Antarctic Territory', 'British Indian Ocean Territory', 'British Sovereign Base Areas', 'British Virgin Islands', 'Brunei', 'Bulgaria', 'Burkina Faso', 'Burundi', 'Cambodia', 'Cameroon', 'Canada', 'Cape Verde', 'Cayman Islands', 'Central African Republic', 'Chad', 'Chile', "China, People's Republic of", 'China, Republic of (Taiwan)', 'Christmas Island', 'Clipperton Island', 'Cocos (Keeling) Islands', 'Colombia', 'Comoros', 'Congo, (Congo Brazzaville)', 'Congo, (Congo Kinshasa)', 'Cook Islands', 'Coral Sea Islands', 'Costa Rica', "Cote d'Ivoire (Ivory Coast)", 'Croatia', 'Cuba', 'Cyprus', 'Czech Republic', 'Denmark', 'Djibouti', 'Dominica', 'Dominican Republic', 'Ecuador', 'Egypt', 'El Salvador', 'Equatorial Guinea', 'Eritrea', 'Estonia', 'Ethiopia', 'Falkland Islands (Islas Malvinas)', 'Faroe Islands', 'Fiji', 'Finland', 'France', 'French Guiana', 'French Polynesia', 'French Southern and Antarctic Lands', 'Gabon', 'Gambia, The', 'Georgia', 'Germany', 'Ghana', 'Gibraltar', 'Greece', 'Greenland', 'Grenada', 'Guadeloupe', 'Guam', 'Guatemala', 'Guernsey', 'Guinea', 'Guinea-Bissau', 'Guyana', 'Haiti', 'Heard Island and McDonald Islands', 'Honduras', 'Hong Kong', 'Howland Island', 'Hungary', 'Iceland', 'India', 'Indonesia', 'Iran', 'Iraq', 'Ireland', 'Isle of Man', 'Israel', 'Italy', 'Jamaica', 'Japan', 'Jarvis Island', 'Jersey', 'Johnston Atoll', 'Jordan', 'Kazakhstan', 'Kenya', 'Kingman Reef', 'Kiribati', 'Korea, North', 'Korea, South', 'Kuwait', 'Kyrgyzstan', 'Laos', 'Latvia', 'Lebanon', 'Lesotho', 'Liberia', 'Libya', 'Liechtenstein', 'Lithuania', 'Luxembourg', 'Macau', 'Macedonia', 'Madagascar', 'Malawi', 'Malaysia', 'Maldives', 'Mali', 'Malta', 'Marshall Islands', 'Martinique', 'Mauritania', 'Mauritius', 'Mayotte', 'Mexico', 'Micronesia', 'Midway Islands', 'Moldova', 'Monaco', 'Mongolia', 'Montenegro', 'Montserrat', 'Morocco', 'Mozambique', 'Myanmar (Burma)', 'Nagorno-Karabakh', 'Namibia', 'Nauru', 'Navassa Island', 'Nepal', 'Netherlands', 'Netherlands Antilles', 'New Caledonia', 'New Zealand', 'Nicaragua', 'Niger', 'Nigeria', 'Niue', 'Norfolk Island', 'Northern Cyprus', 'Northern Mariana Islands', 'Norway', 'Oman', 'Pakistan', 'Palau', 'Palmyra Atoll', 'Panama', 'Papua New Guinea', 'Paraguay', 'Peru', 'Peter I Island', 'Philippines', 'Pitcairn Islands', 'Poland', 'Portugal', 'Pridnestrovie (Transnistria)', 'Puerto Rico', 'Qatar', 'Queen Maud Land', 'Reunion', 'Romania', 'Ross Dependency', 'Russia', 'Rwanda', 'Saint Barthelemy', 'Saint Helena', 'Saint Kitts and Nevis', 'Saint Lucia', 'Saint Martin', 'Saint Pierre and Miquelon', 'Saint Vincent and the Grenadines', 'Samoa', 'San Marino', 'Sao Tome and Principe', 'Saudi Arabia', 'Senegal', 'Serbia', 'Seychelles', 'Sierra Leone', 'Singapore', 'Slovakia', 'Slovenia', 'Solomon Islands', 'Somalia', 'Somaliland', 'South Africa', 'South Georgia & South Sandwich Islands', 'South Ossetia', 'Spain', 'Sri Lanka', 'Sudan', 'Suriname', 'Svalbard', 'Swaziland', 'Sweden', 'Switzerland', 'Syria', 'Tajikistan', 'Tanzania', 'Thailand', 'Timor-Leste (East Timor)', 'Togo', 'Tokelau', 'Tonga', 'Trinidad and Tobago', 'Tristan da Cunha', 'Tunisia', 'Turkey', 'Turkmenistan', 'Turks and Caicos Islands', 'Tuvalu', 'U.S. Virgin Islands', 'Uganda', 'Ukraine', 'United Arab Emirates', 'United Kingdom', 'United States', 'Uruguay', 'Uzbekistan', 'Vanuatu', 'Vatican City', 'Venezuela', 'Vietnam', 'Wake Island', 'Wallis and Futuna', 'Yemen', 'Zambia', 'Zimbabwe' );
33 | ?>
34 |
35 | >
36 |
37 | >
38 |
39 |
40 |
41 |
42 |
43 |
35 |
36 |
37 | ID, 'tix_attended', true ) ); ?> />
38 |
39 |
40 |
41 | ID, 'tix_attended', true ) ) {
89 | $camptix->increment_summary( $summary, __( 'Attended', 'camptix' ) );
90 | }
91 | }
92 |
93 | /**
94 | * Add the 'Attended the event' column to the attendee export
95 | *
96 | * @param array $extra_columns
97 | * @return array
98 | */
99 | public function add_extra_report_columns( $extra_columns ) {
100 | $extra_columns['attended'] = __( 'Attended the event', 'camptix' );
101 |
102 | return $extra_columns;
103 | }
104 |
105 | /**
106 | * Set the value for the 'Attended the Event' column for the given attendee in the attendee export
107 | *
108 | * @param string $value
109 | * @param WP_Post $attendee
110 | * @return string
111 | */
112 | public function add_report_value_attended( $value, $attendee ) {
113 | return get_post_meta( $attendee->ID, 'tix_attended', true ) ? 'Yes' : 'No';
114 | }
115 |
116 | /**
117 | * Add extra columns to the Attendees screen.
118 | *
119 | * @param array $columns
120 | * @return array
121 | */
122 | public function add_custom_columns( $columns ) {
123 | $columns = array_merge( array( 'attended' => __( 'Attended', 'camptix' ) ), $columns );
124 |
125 | return $columns;
126 | }
127 |
128 | /**
129 | * Render custom columns on the Attendees screen.
130 | *
131 | * @param string $column
132 | * @param int $attendee_id
133 | */
134 | public function render_custom_columns( $column, $attendee_id ) {
135 | switch ( $column ) {
136 | case 'attended':
137 | $attendee = get_post( $attendee_id );
138 |
139 | if ( 'publish' != $attendee->post_status ) {
140 | break;
141 | }
142 |
143 | if ( get_post_meta( $attendee_id, 'tix_attended', true ) ) {
144 | _e( 'Attended', 'camptix' );
145 | } else {
146 | ?>
147 |
148 |
153 |
154 |
155 |
156 |
157 |
173 |
174 |
177 |
178 |
181 |
182 |
191 |
192 | 'Required parameters not set.' ) );
201 | }
202 |
203 | $attendee_id = absint( $_REQUEST['attendee_id'] );
204 |
205 | if ( ! wp_verify_nonce( $_REQUEST['nonce'], 'tix_mark_attended_' . $attendee_id ) || ! current_user_can( 'edit_post', $attendee_id ) ) {
206 | wp_send_json_error( array( 'error' => 'Permission denied.' ) );
207 | }
208 |
209 | $attendee = get_post( $attendee_id );
210 |
211 | if ( ! is_a( $attendee, 'WP_Post' ) || 'tix_attendee' != $attendee->post_type ) {
212 | wp_send_json_error( array( 'error' => 'Invalid attendee.' ) );
213 | }
214 |
215 | update_post_meta( $attendee_id, 'tix_attended', true );
216 | wp_send_json_success();
217 | }
218 | }
219 |
220 | // Register this class as a CampTix Addon.
221 | camptix_register_addon( 'CampTix_Track_Attendance' );
222 |
--------------------------------------------------------------------------------
/readme.txt:
--------------------------------------------------------------------------------
1 | === CampTix Event Ticketing ===
2 | Contributors: automattic, kovshenin, andreamiddleton, iandunn, coreymckrill
3 | Tags: ticketing, event ticketing
4 | Requires at least: 3.5
5 | Tested up to: 5.0
6 | Stable tag: 1.7.0
7 | Donate link: http://wordpressfoundation.org/donate/
8 | License: GPLv2 or later
9 | License URI: http://www.gnu.org/licenses/gpl-2.0.html
10 |
11 | Simple and Flexible ticketing brought to you by WordCamp.org
12 |
13 | == Description ==
14 |
15 | CampTix is an easy to use and flexible event ticketing plugin created by WordCamp.org. Allow visitors to purchase tickets to your online or offline event, directly from your WordPress website.
16 |
17 | * Multiple tickets and attendees forms
18 | * Coupon codes for discounts
19 | * Mass e-mail attendees
20 | * Export attendees data into CSV or XML
21 | * Public attendees list
22 | * Revenue reports and summaries
23 | * Refund purchased tickets
24 | * and much more!
25 |
26 | Feel free to post your feature requests, issues and pull requests to [CampTix on GitHub](https://github.com/automattic/camptix "CampTix on GitHub").
27 |
28 | To contribute or update a translation, visit [the translation project](https://translate.wordpress.org/projects/wp-plugins/camptix "CampTix translation project").
29 |
30 | == Installation ==
31 |
32 | 1. Download and extract CampTix in your `wp-content/plugins` directory
33 | 1. Activate the plugin through the Plugins menu in WordPress
34 | 1. Go to Tickets - Setup to configure your event settings and payment methods
35 | 1. Create a ticket or two, place the `[camptix]` shortcode on a Page
36 | 1. Start selling!
37 |
38 | For more information, visit the [Getting Started](https://github.com/automattic/camptix/wiki "Getting Started") guide on CampTix Wiki.
39 |
40 | == Screenshots ==
41 |
42 | 1. Ticket sales table
43 | 2. Attendee registration form
44 | 3. Attendee admin view
45 | 4. Summarize by ticket type
46 | 5. Summarize by purchase day of week
47 | 6. Revenue report
48 | 7. Mass e-mail attendees
49 |
50 | == Changelog ==
51 |
52 | = 1.7.0 (2018-07-09) =
53 | * [NEW] Added support for Stripe as a payment method.
54 | * [NEW] Added support for many new currencies, including INR, PKR, and ZAR. The Stripe payment method must be enabled in order to use them.
55 | * [NEW] Added support for WordPress Core's personal data export and erasure tools.
56 | * [NEW] Added data handling details for WordPress Core's privacy policy tool.
57 | * [NEW] Allowed sanitized HTML in ticket title and excerpt.
58 | * [NEW] Added the `camptix_shortcode_contents` filter hook to support modifying camptix shortcode output.
59 | * [NEW] Added the `camptix_attendee_form_before_questions` and `camptix_attendee_form_after_questions` action hooks, which can be used to insert additional form elements.
60 | * [NEW] Added the `camptix_form_attendee_info_errors` action hook for adding custom error messages on the Attendee Info form.
61 | * [FIX] Added a workaround for systems (such as Windows) where the `money_format()` function is not available.
62 | * [FIX] The `logged_out_message` parameter in the `camptix_private` shortcode was not functional.
63 | * [FIX] The `camptix_attendees` shortcode did not handle array values. Now it converts the array to a comma separated string on render.
64 | * [Full changelog](https://github.com/Automattic/camptix/compare/69dc5368bd0df25d4a41b7bde7217f0c8c809c9a...343e2f31d35cd9bcb467f59fd43dbc5481a3f71b)
65 |
66 | = 1.6.0 (2017-03-10) =
67 | * [NEW] Enabled compatibility with language packs. [See status of translation locales](https://translate.wordpress.org/projects/wp-plugins/camptix).
68 | * [NEW] Addon to collect and track tshirt sizes for attendees, plus a shortcode to display aggregated tshirt size data.
69 | * [NEW] Enhanced the [camptix_attendees] shortcode to lazy-load attendee gravatars.
70 | * [Full changelog](https://github.com/Automattic/camptix/compare/0855047c86ef30ae8f72094899a412d2f7d27a7d...2ec1ec005d490dc627b3b0df2b1dd33491962d84)
71 |
72 | = 1.5.1 (2016-10-11) =
73 | * [SECURITY] Fixed 3 CSV injection bypasses reported in [#160500-h1](https://hackerone.com/reports/160500), [#160520-h1](https://hackerone.com/reports/160520), and [#160674-h1](https://hackerone.com/reports/160674). Props to [white_walker](https://hackerone.com/white_walker), [lalka](https://hackerone.com/lalka), and [grande](https://hackerone.com/grande) for discovery and coordinated disclosure.
74 | * [SECURITY] Fixed an XSS vulnerability reported in [#164793-h1](https://hackerone.com/reports/164793). Props to [grande](https://hackerone.com/grande) for discovery and coordinated disclosure.
75 |
76 | = 1.5 (2016-08-10) =
77 | * [SECURITY] Fixed [CSV injection vulnerability](https://hackerone.com/reports/151516) with CVSS score of [8.3](https://www.first.org/cvss/calculator/3.0#CVSS:3.0/AV:N/AC:H/PR:N/UI:R/S:C/C:H/I:H/A:H). Props to [Zawad Bin Hafiz](https://hackerone.com/thezawad) for discovery and coordinated disclosure.
78 | * [SECURITY] Fixed [XSS vulnerability](https://hackerone.com/reports/152958) with CVSS score of [4.8](https://www.first.org/cvss/calculator/3.0#CVSS:3.0/AV:N/AC:L/PR:H/UI:R/S:C/C:L/I:L/A:N). Props to [Zawad Bin Hafiz](https://hackerone.com/thezawad) for discovery and coordinated disclosure.
79 | * [NEW] Added ability to send Notify emails to segments of attendees.
80 | * [NEW] Added a built-in Ticket Question for attendee's country.
81 | * [NEW] Added partial support for HTML emails (requires custom template).
82 | * [NEW] Added `payment method` field to Attendee Information meta box and CSV export.
83 | * [FIX] Improved currency formatting in non-English locales.
84 | * [FIX] Fixed bug where PayPal transactions would sometimes be rejected in multibyte languages.
85 | * [FIX] Fixed bug where not all attendees were displayed in the Attendees shortcode.
86 | * [Full changelog](https://github.com/Automattic/camptix/compare/30b2d16...294552c41f88704c85dd126d17d89df2523b7cb4)
87 |
88 | = 1.4.2 =
89 | * Added a nonce check for privacy and attendance toggles for better security
90 | * Various small i18n fixes and language updates
91 | * Various new actions and filters for more flexibility
92 | * New addon that allows admins to track attendance
93 | * New addon that allows admins to require users to be logged in to purchase a ticket
94 | * Removed pending attendees from revenue reports
95 | * [Full changelog](https://github.com/Automattic/camptix/compare/80b2d7997272aea68fa0cfb509d3d72f15cec18a...a9487f954f3013e698e7991c8f12e86ae85234ae)
96 |
97 | = 1.4.1 =
98 | * Updated PayPal module to use HTTP 1.1 now that PayPal requires it. Fixes "A payment error has occurred" errors.
99 | * Added support for Eastern name ordering.
100 | * Updated Japanese and French translations.
101 | * Add Slovak translation.
102 | * Fixes E_STRICT notices in PHP 5.4.
103 | * Adds [camptix_stats] shortcode.
104 | * [Full changelog](https://github.com/Automattic/camptix/compare/6c2ff5413d6294b0fca6abc0ebd9124a6b9399f8...e71760abbfb025f4184e329e4c029c694a4d3a01)
105 |
106 | = 1.4 =
107 | * Attendees can automatically refund their tickets
108 | * More e-mail templates are customizable
109 | * Added translations for Swedish (Jonathan De Jong), German (Raphael Michel), Japanese (Naoko Takano), Russian (Konstantin Kovshenin), and Portuguese (Rafael Funchal)
110 | * New actions and filters for customization
111 | * Fixed a bug where the [camptix] shortcode would break when used on the homepage
112 | * Reintroduced the Refund All Tickets feature
113 | * Handles duplicate requests from PayPal more gracefully, so attendees aren't set to a failed status
114 | * Added a checkbox to toggle the Attendee privacy feature
115 | * Added an upgrade command for WP-CLI
116 | * [Full changelog](https://github.com/Automattic/camptix/compare/826cc2b...a53af6d)
117 |
118 | = 1.3.1 =
119 | * Better escaping and sanitization
120 | * Better error messages during failed payments
121 | * Fixed a bug where the shortcode would display in plain text
122 | * Other minor bug fixes and clean ups
123 |
124 | = 1.3 =
125 | * Added the ability to edit confirmation e-mails
126 | * Reworked ticket questions, both under the hood and UI
127 | * Added support to edit new and existing questions
128 | * Added a bunch of currencies for PayPal
129 | * Few bug fixes and minor enhancements
130 |
131 | = 1.2.1 =
132 | * Numerous bugs fixed
133 | * RTL stylesheets
134 | * Predefined PayPal credentials with a filter
135 | * French and Hebrew translations: props xibe and maor
136 | * New currency: ILS
137 |
138 | = 1.2 =
139 | * Added and API for payment methods
140 | * Enhanced logging around payments
141 | * UI cleanup in ticket questions
142 | * Invalidate attendees list shortcode when an attendee is changed
143 | * Improved admin columns in attendees, tickets and coupons
144 | * Added GBP currency to PayPal
145 | * Enabled meta logging addon by default
146 | * Added textarea and radio question types
147 | * Added column attribute to the [camptix_attendees] shortcode
148 | * Added a couple of language packs
149 | * Minor cleanups and bugfixes
150 |
151 | = 1.1 =
152 | * Added JPY currency
153 | * Added l10n functions
154 | * Removing closure functions to support php 5.2
155 | * Questions v2 now a public feature
156 | * Minor cleanups, bugfixes and enhancements
157 |
158 | = 1.0 =
159 | * First version
160 |
--------------------------------------------------------------------------------
/admin.css:
--------------------------------------------------------------------------------
1 | .tix-clear {
2 | height: 0;
3 | line-height: 0;
4 | display: block;
5 | font-size: 0;
6 | clear: both;
7 | }
8 |
9 | .tix-table {
10 | width: 100%;
11 | border-spacing: 0;
12 | }
13 | .tix-table span {
14 | display: inline-block;
15 | padding: 4px 0;
16 | }
17 |
18 | #tix_ticket_options input,
19 | #tix_ticket_availability input {
20 | margin: 0;
21 | }
22 |
23 | #tix_ticket_options .inside,
24 | #tix_ticket_availability .inside,
25 | #tix_attendee_submitdiv .inside {
26 | margin: 0;
27 | padding: 0;
28 | }
29 | #tix_ticket_options .misc-pub-section span.left,
30 | #tix_ticket_availability .misc-pub-section span.left {
31 | display: inline-block;
32 | width: 70px;
33 | }
34 |
35 | #tix_ticket_availability .date,
36 | .tix-date-field {
37 | width: 80px;
38 | }
39 |
40 | #postexcerpt p {
41 | display: none;
42 | }
43 |
44 | /*.tix-ticket-questions span,
45 | .tix-ticket-reservations span {
46 | display: inline-block;
47 | padding: 8px 0 8px 0px;
48 | margin: 0 0px;
49 | }*/
50 |
51 | #tix_ticket_questions .inside {
52 | margin: 0;
53 | padding: 0;
54 | }
55 |
56 | .tix-ticket-questions .tix-item-inner-left,
57 | .tix-ticket-questions .tix-item-inner-middle,
58 | .tix-ticket-questions .tix-item-inner-right {
59 | padding-top: 8px;
60 | padding-bottom: 4px;
61 | }
62 |
63 | .tix-ticket-questions .tix-item-inner-left {
64 | width: 50px;
65 | float: left;
66 | text-align: right;
67 | }
68 |
69 | .tix-ticket-questions .tix-item-inner-middle {
70 | margin-left: 58px;
71 | }
72 |
73 | .tix-ticket-questions .tix-item-inner-right {
74 | float: right;
75 | padding-left: 8px;
76 | padding-right: 8px;
77 | padding-top: 4px;
78 | text-transform: uppercase;
79 | }
80 |
81 | .tix-ticket-questions .tix-item-inner-right a {
82 | display: inline-block;
83 | padding-bottom: 2px;
84 | text-decoration: none;
85 | margin-right: 4px;
86 | color: #999;
87 | }
88 |
89 | .tix-ticket-questions .tix-item-inner-right a:hover,
90 | .tix-ticket-questions .tix-item-inner-right a:focus {
91 | color: #333;
92 | }
93 |
94 | .tix-ticket-questions .tix-item-inner-right a.tix-item-delete:hover {
95 | color: red;
96 | }
97 |
98 | .tix-ticket-questions span.tix-field-type {
99 | font-size: 8px;
100 | font-weight: 600;
101 | text-transform: uppercase;
102 | color: #999;
103 | }
104 | .tix-ticket-questions .tix-item-sort-handle {
105 | cursor: move;
106 | }
107 | .tix-ui-state-highlight {
108 | background: white;
109 | }
110 | .tix-ticket-questions span.tix-field-name {
111 | }
112 | .tix-ticket-questions span.tix-field-values {
113 | display: block;
114 | margin-top: 4px;
115 | margin-bottom: 4px;
116 | color: #999;
117 | }
118 | .tix-ticket-questions .tix-item {
119 | border-bottom: solid 1px #DFDFDF;
120 | border-top: solid 1px white;
121 | background-color: whiteSmoke;
122 | }
123 | .tix-ticket-questions .tix-item-highlight {
124 | background: white;
125 | height: 36px;
126 | width: 100%;
127 | }
128 |
129 | .tix-ticket-questions .tix-item-inner {
130 |
131 | }
132 |
133 | .tix-item.tix-prototype {
134 | display: none;
135 | }
136 | #tix-add-question-existing-list {
137 | width: 90%;
138 | }
139 | .tix-existing-question.tix-disabled label {
140 | color: #ccc;
141 | }
142 | .tix-item .tix-field-required-star {
143 | display: none;
144 | }
145 | .tix-item.tix-item-required .tix-field-required-star {
146 | display: inline-block;
147 | color: #DD0000;
148 | font-size: 14px;
149 | }
150 |
151 | .tix-add-question {
152 | padding: 8px 8px 8px 58px;
153 | }
154 |
155 | #tix-add-question-new-form,
156 | #tix-add-question-existing-form {
157 | display: none;
158 | }
159 |
160 | .tix-ticket-questions .column-required,
161 | .tix-ticket-reservations .column-quantity,
162 | .tix-ticket-reservations .column-used,
163 | .tix-ticket-reservations .column-token,
164 | .tix-ticket-reservations .column-actions {
165 | text-align: center;
166 | }
167 |
168 | .tix-ticket-reservations .column-actions input.button {
169 | width: auto !important;
170 | }
171 |
172 | .tix-ticket-questions input[type="checkbox"] {
173 | width: auto !important;
174 | }
175 |
176 | .tix-ticket-reservations span {
177 | display: inline-block;
178 | margin: 0px 8px 0px 8px;
179 | padding: 0 4px;
180 | }
181 |
182 | #tix_coupon_options input,
183 | #tix_coupon_availability input {
184 | margin: 0;
185 | }
186 | #tix_coupon_options .inside,
187 | #tix_coupon_availability .inside {
188 | margin: 0;
189 | padding: 0;
190 | }
191 | #tix_coupon_options .misc-pub-section span.left,
192 | #tix_coupon_availability .misc-pub-section span.left {
193 | display: block;
194 | width: 70px;
195 | float: left;
196 | }
197 |
198 | #tix_coupon_options .date,
199 | #tix_coupon_availability .date {
200 | width: 80px;
201 | }
202 |
203 | .tix-checkbox-group {
204 | display: block;
205 | margin-left: 72px;
206 | }
207 |
208 | .tix-checkbox-group label {
209 | display: block;
210 | }
211 |
212 | #tix_attendee_info .inside,
213 | #tix_log .inside,
214 | #tix_db_log .inside {
215 | margin: 0;
216 | padding: 0;
217 | }
218 |
219 | .tix-revenue-summary .tix-sold,
220 | .tix-revenue-summary .tix-remaining,
221 | .tix-revenue-summary .tix-sub-total,
222 | .tix-revenue-summary .tix-discounted,
223 | .tix-revenue-summary .tix-revenue {
224 | text-align: right;
225 | }
226 |
227 | .tix-revenue-summary .tix-row-total {
228 | font-weight: bold;
229 | }
230 |
231 | .tix-log-table td.tix-0 {
232 | width: 120px;
233 | vertical-align: top;
234 | }
235 |
236 | .tix-attendees-info td,
237 | .tix-log-table td {
238 | padding-left: 8px;
239 | padding-right: 8px;
240 | border-top: solid 1px white;
241 | border-bottom: solid 1px #DFDFDF;
242 | vertical-align: top;
243 | }
244 | .tix-attendees-info tr.tix-row-general td,
245 | .tix-attendees-info tr.tix-row-transaction td,
246 | .tix-attendees-info tr.tix-row-questions td {
247 | font-weight: bold;
248 | }
249 |
250 | abbr.tix-column-label {
251 | cursor: pointer;
252 | }
253 |
254 | #tix-notify-preview {
255 | background: #F5EFC6;
256 | width: 500px;
257 | padding: 20px;
258 | overflow: hidden;
259 | }
260 |
261 | label.tix-yes-no {
262 | margin-right: 20px;
263 | }
264 |
265 | .tix-setup-form {
266 | max-width: 650px;
267 | }
268 |
269 | #icon-edit.icon32-posts-tix_ticket {
270 | background-image: url('images/icons.png');
271 | background-position: -39px -11px;
272 | background-size: 196px 168px;
273 | }
274 |
275 | #dashboard_right_now a.tix_attendee-count:before {
276 | content: "\f307";
277 | }
278 |
279 | /*
280 | * Track Attendance addon
281 | */
282 | body.post-type-tix_attendee table.posts td.attended {
283 | text-align: center;
284 | }
285 |
286 | body.post-type-tix_attendee table.posts td.attended div.spinner {
287 | display: block;
288 | float: none;
289 | margin-left: auto;
290 | margin-right: auto;
291 | }
292 |
293 | /*
294 | * Media queries
295 | */
296 | @media only screen and (-webkit-min-device-pixel-ratio: 1.5) {
297 | #icon-edit.icon32-posts-tix_ticket {
298 | background-size: 98px 84px;
299 | background-position: -52px -11px;
300 | }
301 | }
302 |
303 | @media screen and ( max-width: 782px ) {
304 | body.post-type-tix_attendee table.posts th.column-title {
305 | width: auto;
306 | }
307 |
308 | .fixed .column-tix_email,
309 | .fixed .column-tix_coupon,
310 | .fixed .column-tix_ticket_price,
311 | .fixed .column-tix_reservation,
312 | .fixed .column-tix_order_total {
313 | display: none;
314 | }
315 |
316 | body.post-type-tix_attendee table.posts.fixed .column-attended::before {
317 | content: "";
318 | }
319 | }
320 |
321 | @media screen and ( max-width: 535px ) {
322 | .fixed .column-tix_ticket {
323 | display: none;
324 | }
325 | }
326 |
327 | .tix-match {
328 | margin-bottom: 16px;
329 | }
330 |
331 | .tix-segment-item {
332 | max-width: 100%;
333 | }
334 |
335 | .tix-segment {
336 | width: 100%;
337 | position: relative;
338 | }
339 |
340 | .tix-delete-segment-condition {
341 | position: absolute;
342 | margin-left: -22px;
343 | margin-top: 5px;
344 | color: #666;
345 | }
346 |
347 | .tix-segment .segment-field-wrap {
348 | float: left;
349 | width: 40%;
350 | margin-right: 1%;
351 | }
352 |
353 | .tix-segment .segment-op-wrap {
354 | float: left;
355 | width: 10%;
356 | margin-right: 1%;
357 | }
358 |
359 | .tix-segment .segment-value-wrap {
360 | float: left;
361 | width: 48%;
362 | }
363 |
364 | .tix-segment .segment-field,
365 | .tix-segment .segment-op,
366 | .tix-segment .segment-value {
367 | width: 100%;
368 | }
369 |
370 | .tix-add-segment-condition {
371 | margin-top: 16px;
372 | }
373 |
374 | /**
375 | * === RTL Styles ===
376 | * Should always be at the EOF, since they need to override
377 | * some of the default styles in RTL environments.
378 | * RTL installs should have a class appended to
379 | * named 'rtl'. We're going to use it to line things up.
380 | */
381 | .rtl .tix-ticket-questions .tix-item-inner-left {
382 | float: right;
383 | text-align: left;
384 | width: 70px;
385 | }
386 | .rtl .tix-ticket-questions .tix-item-inner-middle {
387 | margin-left: 0;
388 | margin-right: 78px;
389 | }
390 | .rtl .tix-ticket-questions span.tix-field-type {
391 | font-size: 11px;
392 | font-weight: bold;
393 | }
394 | .rtl .tix-ticket-questions .tix-item-inner-right { float: left; }
395 |
396 | .rtl .tix-log-table { direction: ltr; }
397 |
398 | .rtl #tix_coupon_options .misc-pub-section span.left,
399 | .rtl #tix_coupon_availability .misc-pub-section span.left { float: right; }
400 |
--------------------------------------------------------------------------------
/inc/class-camptix-payment-method.php:
--------------------------------------------------------------------------------
1 | false,
15 | 'refund-all' => false,
16 | );
17 |
18 | /**
19 | * Constructor
20 | */
21 | function __construct() {
22 | /** @var $camptix CampTix_Plugin */
23 | global $camptix;
24 |
25 | parent::__construct();
26 |
27 | add_filter( 'camptix_available_payment_methods', array( $this, '_camptix_available_payment_methods' ) );
28 | add_filter( 'camptix_validate_options', array( $this, '_camptix_validate_options' ) );
29 | add_filter( 'camptix_get_payment_method_by_id', array( $this, '_camptix_get_payment_method_by_id' ), 10, 2 );
30 |
31 | $payment_method = get_class( $this );
32 |
33 | if ( ! $this->id ) {
34 | wp_die( "ID not specified in $payment_method." );
35 | }
36 |
37 | if ( ! $this->name ) {
38 | wp_die( "Name not specified in $payment_method." );
39 | }
40 |
41 | if ( ! $this->description ) {
42 | wp_die( "Description not specified in $payment_method." );
43 | }
44 |
45 | if ( ! is_array( $this->supported_currencies ) || count( $this->supported_currencies ) < 1 ) {
46 | wp_die( "Supported currencies not specified in $payment_method." );
47 | }
48 |
49 | $this->camptix_options = $camptix->get_options();
50 | if (
51 | array_key_exists( 'payment_methods', $this->camptix_options )
52 | && array_key_exists( $this->id, $this->camptix_options['payment_methods'] )
53 | && $this->camptix_options['payment_methods'][ $this->id ]
54 | ) {
55 | add_filter( 'camptix_supported_currencies', array( $this, 'add_supported_currency' ) );
56 | }
57 | }
58 |
59 | /**
60 | * Add currencies supported by this plugin to the global list of currencies. Payment addons should not override
61 | * this method, but instead define an array `$supported_currencies`
62 | *
63 | * @param $currencies
64 | *
65 | * @return array
66 | */
67 | function add_supported_currency( $currencies ) {
68 | return array_merge( $this->supported_currencies, $currencies );
69 | }
70 |
71 | /**
72 | * Handle calls to inaccessible methods
73 | *
74 | * In the past, this class duplicated some methods from the CampTix_Plugin class, and had wrappers that would
75 | * do nothing except call public methods in the CampTix_Plugin class. That is unnecessary and undesirable,
76 | * though, as those methods should just be called directly. Those methods were removed, and this method was added
77 | * to maintain backwards-compatibility with any addons that are still calling the methods on this class.
78 | *
79 | * @param string $name
80 | * @param array $arguments
81 | *
82 | * @return mixed the function result, or false on error.
83 | */
84 | public function __call( $name, $arguments ) {
85 | /** @var $camptix Camptix_Plugin */
86 | global $camptix;
87 |
88 | // Whitelist the methods we want to use to avoid unintentionally calling CampTix_Plugin methods in case of typos, etc
89 | $camptix_methods = array( 'payment_result', 'redirect_with_error_flags', 'error_flag', 'get_tickets_url', 'log', 'field_text', 'field_checkbox', 'field_yesno' );
90 |
91 | if ( in_array( $name, $camptix_methods ) ) {
92 | // Set a default value for the log $module parameter
93 | if ( 'log' == $name && empty( $arguments[4] ) ) {
94 | $arguments[4] = 'payment';
95 | }
96 |
97 | return call_user_func_array( array( $camptix, $name ), $arguments );
98 | } else {
99 | trigger_error( sprintf( 'Call to undefined method %s::%s()', get_class( $this ), $name ), E_USER_ERROR );
100 | }
101 | }
102 |
103 | /**
104 | * Check if the payment method supports the given currency
105 | *
106 | * @param string $currency
107 | *
108 | * @return bool
109 | */
110 | function supports_currency( $currency ) {
111 | return in_array( $currency, $this->supported_currencies );
112 | }
113 |
114 | /**
115 | * Check if the payment method supports the given feature
116 | *
117 | * @param string $feature
118 | *
119 | * @return bool
120 | */
121 | function supports_feature( $feature ) {
122 | return array_key_exists( $feature, $this->supported_features ) ? $this->supported_features[ $feature ] : false;
123 | }
124 |
125 | /**
126 | * Get the payment gateway object for the given ID
127 | *
128 | * @param CampTix_Payment_Method $payment_method
129 | * @param string $id
130 | *
131 | * @return CampTix_Payment_Method
132 | */
133 | function _camptix_get_payment_method_by_id( $payment_method, $id ) {
134 | if ( $this->id == $id ) {
135 | $payment_method = $this;
136 | }
137 |
138 | return $payment_method;
139 | }
140 |
141 | /**
142 | * Render the section header markup on the Payment screen
143 | */
144 | function _camptix_settings_section_callback() {
145 | echo '' . $this->description . '
';
146 | printf( '' . __( 'Supported currencies: %s.', 'camptix' ) . '
', implode( ', ', $this->supported_currencies ) );
147 | }
148 |
149 | /**
150 | * Render the markup for the Enabled button
151 | *
152 | * @param array $args
153 | */
154 | function _camptix_settings_enabled_callback( $args = array() ) {
155 | /** @var $camptix CampTix_Plugin */
156 | global $camptix;
157 |
158 | if ( in_array( $this->camptix_options['currency'], $this->supported_currencies ) ) {
159 | $camptix->field_yesno( $args );
160 | } else {
161 | _e( 'Disabled', 'camptix' );
162 |
163 | ?>
164 |
165 |
166 | ' . esc_html( $this->camptix_options['currency'] ) . '
'
169 | ); ?>
170 |
171 |
172 | id}";
185 | $option_key = "payment_options_{$this->id}";
186 |
187 | if ( ! isset( $_POST[ $post_key ] ) ) {
188 | return $camptix_options;
189 | }
190 |
191 | $input = $_POST[ $post_key ];
192 | $output = $this->validate_options( $input );
193 | $camptix_options[ $option_key ] = $output;
194 |
195 | return $camptix_options;
196 | }
197 |
198 | /**
199 | * Validate new option values before saving
200 | *
201 | * @param array $input
202 | *
203 | * @return array
204 | */
205 | function validate_options( $input ) {
206 | return array();
207 | }
208 |
209 | /**
210 | * Handle the checkout process
211 | *
212 | * @param string $payment_token
213 | *
214 | * @return int A payment status, e.g., PAYMENT_STATUS_CANCELLED, PAYMENT_STATUS_COMPLETED, etc
215 | */
216 | abstract function payment_checkout( $payment_token );
217 |
218 | /**
219 | * Handle the refund process
220 | *
221 | * @param string $payment_token
222 | *
223 | * @return int A payment status, e.g., PAYMENT_STATUS_CANCELLED, PAYMENT_STATUS_COMPLETED, etc
224 | */
225 | function payment_refund( $payment_token ) {
226 | /** @var $camptix Camptix_Plugin */
227 | global $camptix;
228 |
229 | $refund_data = array();
230 | $camptix->log( __FUNCTION__ . ' not implemented in payment module.', 0, null, 'refund' );
231 |
232 | return $this->payment_result( $payment_token, CampTix_Plugin::PAYMENT_STATUS_REFUND_FAILED, $refund_data );
233 | }
234 |
235 | /**
236 | * Send a request for a refund to the payment gateway API
237 | *
238 | * @param string $payment_token
239 | *
240 | * @return array
241 | */
242 | function send_refund_request( $payment_token ) {
243 | /** @var $camptix Camptix_Plugin */
244 | global $camptix;
245 |
246 | $result = array(
247 | 'token' => $payment_token,
248 | 'status' => CampTix_Plugin::PAYMENT_STATUS_REFUND_FAILED,
249 | 'refund_transaction_id' => null,
250 | 'refund_transaction_details' => array()
251 | );
252 |
253 | $camptix->log( __FUNCTION__ . ' not implemented in payment module.', 0, null, 'refund' );
254 | return $result;
255 | }
256 |
257 | /**
258 | * Register settings for the Payment screen
259 | */
260 | function payment_settings_fields() {
261 | }
262 |
263 | /**
264 | * Add the current payment method to the list of available methods
265 | *
266 | * @param array $payment_methods
267 | *
268 | * @return array
269 | */
270 | function _camptix_available_payment_methods( $payment_methods ) {
271 | if ( $this->id && $this->name && $this->description ) {
272 | $payment_methods[ $this->id ] = array(
273 | 'name' => $this->name,
274 | 'description' => $this->description,
275 | );
276 | }
277 |
278 | return $payment_methods;
279 | }
280 |
281 | /**
282 | * Get the order for the given payment token
283 | *
284 | * @param string $payment_token
285 | *
286 | * @return array
287 | */
288 | function get_order( $payment_token = false ) {
289 | if ( ! $payment_token ) {
290 | return array();
291 | }
292 |
293 | $attendees = get_posts( array(
294 | 'posts_per_page' => 1,
295 | 'post_type' => 'tix_attendee',
296 | 'post_status' => 'any',
297 | 'meta_query' => array(
298 | array(
299 | 'key' => 'tix_payment_token',
300 | 'compare' => '=',
301 | 'value' => $payment_token,
302 | 'type' => 'CHAR',
303 | ),
304 | ),
305 | ) );
306 |
307 | if ( ! $attendees ) {
308 | return array();
309 | }
310 |
311 | return $this->get_order_by_attendee_id( $attendees[0]->ID );
312 | }
313 |
314 | /**
315 | * Get the order for the given attendee
316 | *
317 | * @param int $attendee_id
318 | *
319 | * @return array
320 | */
321 | function get_order_by_attendee_id( $attendee_id ) {
322 | $order = (array) get_post_meta( $attendee_id, 'tix_order', true );
323 |
324 | if ( $order ) {
325 | $order['attendee_id'] = $attendee_id;
326 | }
327 |
328 | return $order;
329 | }
330 |
331 | /**
332 | * Get an escaped field name for a setting
333 | *
334 | * @param string $name
335 | *
336 | * @return string
337 | */
338 | function settings_field_name_attr( $name ) {
339 | return esc_attr( "camptix_payment_options_{$this->id}[{$name}]" );
340 | }
341 |
342 | /**
343 | * Add a setting field
344 | *
345 | * @param string $option_name
346 | * @param string $title
347 | * @param callable $callback
348 | * @param string $description
349 | */
350 | function add_settings_field_helper( $option_name, $title, $callback, $description = '' ) {
351 | add_settings_field(
352 | 'camptix_payment_' . $this->id . '_' . $option_name,
353 | $title,
354 | $callback,
355 | 'camptix_options',
356 | 'payment_' . $this->id,
357 | array(
358 | 'name' => $this->settings_field_name_attr( $option_name ),
359 | 'value' => $this->options[ $option_name ],
360 | 'description' => $description,
361 | ) );
362 | }
363 |
364 | /**
365 | * Get this payment method's options
366 | *
367 | * @return array
368 | */
369 | function get_payment_options() {
370 | $payment_options = array();
371 | $option_key = "payment_options_{$this->id}";
372 |
373 | if ( isset( $this->camptix_options[ $option_key ] ) ) {
374 | $payment_options = (array) $this->camptix_options[ $option_key ];
375 | }
376 |
377 | return $payment_options;
378 | }
379 | }
380 |
--------------------------------------------------------------------------------
/help.php:
--------------------------------------------------------------------------------
1 | id == 'tix_ticket' || $screen->id == 'edit-tix_ticket' ) {
6 |
7 | $screen->add_help_tab( array(
8 | 'title' => 'Overview',
9 | 'id' => 'tix-overview',
10 | 'content' => '
11 | Tickets
12 | This screen provides access to the tickets (or ticket types) you have created. Each ticket is has various attributes like price and quantity. The total amount of available tickets determines the maximum capacity of the event. Please note that once the ticket has been published, editing things like price or questions can break data consistency, since attendees may have already bought the ticket with the old data. Also, once a ticket has been published, please keep it published. Do not revert to draft, pending or trash.
13 | Use the Screen Options panel to show and hide the columns that matter most.
',
14 | ) );
15 |
16 | if ( $screen->id == 'tix_ticket' )
17 | $screen->add_help_tab( array(
18 | 'title' => 'Excerpt',
19 | 'id' => 'tix-excerpt',
20 | 'content' => "
21 | Excerpt
22 | The ticket excerpt contains the description of the ticket, generally things like whether it include a t-shirt, food, and so on. This will be displayed underneath the ticket title on the ticketing page.
",
23 | ) );
24 |
25 | $screen->add_help_tab( array(
26 | 'title' => 'Price',
27 | 'id' => 'tix-price',
28 | 'content' => '
29 | Price
30 | The ticket price determines how much the user should pay, to obtain the ticket. The currency is set in the CampTix plugin Setup screen. Note that when changing currencies, the existing prices will not be converted, so make sure you get this right the first time.
31 | A price of 0.00 means that any visitor can obtain such a ticket for free. If you want to give out free tickets to certain groups, you should visit the Coupons section.
32 | As soon as at least one ticket has been purchased, the price can no longer be changed. This is made to maintain consistency throughout CampTix reports.',
33 | ) );
34 |
35 | $screen->add_help_tab( array(
36 | 'title' => 'Quantity',
37 | 'id' => 'tix-quantity',
38 | 'content' => '
39 |
Quantity
40 | The quantity of a ticket type is the maximum amount of sales. You can increase quantity over time, but decreasing it will break data consistency. The amount of remaining tickets for every type will be shown to your visitors.
',
41 | ) );
42 |
43 | $screen->add_help_tab( array(
44 | 'title' => 'Availability',
45 | 'id' => 'tix-availability',
46 | 'content' => '
47 | Availability
48 | You can create early-bird tickets, or final-call tickets based on the dates. With Availability, you can set when to start ticket sales, and when to end. Leaving fields blank will set availability to Auto, meaning they can be purchased at any time.
',
49 | ) );
50 |
51 | if ( $screen->id == 'tix_ticket' )
52 | $screen->add_help_tab( array(
53 | 'title' => 'Questions',
54 | 'id' => 'tix-questions',
55 | 'content' => "
56 | Questions
57 | Different tickets may require different information from attendees. For example, one ticket may include a t-shirt while the other can include a wristband. A third ticket may include both. You would ask for a t-shirt size in the first ticket, and a wrist size for the second ticket. You will ask both questions in the third ticket.
58 | Questions can be of different types:
59 |
60 | Text input - a simple textbox where the attendee can enter their website URL, Twitter name, Facebook profile, phone number, etc.
61 | Dropdown select - a pull-down select box where attendees can pick one of several options, like a t-shirt size. The Values column in this case, should contain the different options, separated by a comma.
62 | Checkbox - can be either one or multiple checkboxes. Useful to ask users which presentations they're interested in, whether they make money from WordPress, and so on. The Values field can be empty, can contain a single value which will be used as a label for the checkbox (e.g. 'Yes') or a comma-separated list of multiple values for multiple checkboxes.
63 |
64 | To delete a question, simply remove its field and save the ticket.
",
65 | ) );
66 |
67 | /*if ( $screen->id == 'tix_ticket' )
68 | $screen->add_help_tab( array(
69 | 'title' => 'Reservations',
70 | 'id' => 'tix-reservations',
71 | 'content' => "
72 | Reservations is a way to make sure that a certain group of people, can always purchase their tickets, even if you sell out fast. It's like putting a few tickets aside for your friends or co-workers. Once you create a new reservation, the reservation quantity will be held privately, and accessibly only via the secret link, which you can share with the group you made the reservation for.
73 | Note, that when you create a reservation with more quantity, than the available ticket sales, we'll bump the overall ticket quantity for you. However, when you're releasing a reservation, the quantity is not changed, and the reserved tickets are visible publicly again. Reservations can be used in conjunction with coupons.
",
74 | ) );*/
75 |
76 | } elseif ( $screen->id == 'edit-tix_attendee' || $screen->id == 'tix_attendee' ) {
77 |
78 | $screen->add_help_tab( array(
79 | 'title' => 'Overview',
80 | 'id' => 'tix-overview',
81 | 'content' => "
82 | Attendees
83 | Attendees are people who have purchased (or attempted to purchase) a ticket for the event. A published attendee is one whose payment has been confirmed. A pending attendee is one who has paid, but the payment is not yet confirmed. A draft attendee is one who has filled out the attendee info form during ticket purchase, but never completed the purchase on PayPal. Please don't change the post status manually.
",
84 | ) );
85 |
86 | if ( $screen->id == 'edit-tix_attendee' )
87 | $screen->add_help_tab( array(
88 | 'title' => 'Searching',
89 | 'id' => 'tix-searching',
90 | 'content' => "
91 | Searching
92 | Searching through attendees is easy, on the attendees list, in the top right corner. You can search by name, e-mail, transaction id or even by an answer to the asked questions.
",
93 | ) );
94 |
95 | if ( $screen->id == 'tix_attendee' )
96 | $screen->add_help_tab( array(
97 | 'title' => 'Attendee Information',
98 | 'id' => 'tix-attendee-info',
99 | 'content' => "
100 | Attendee Information
101 | The Attendee Information table will show you everything you need to know about the attendee, the answers to the questions asked by their ticket, their payment status, coupon code as well as the access token, which is a secret link where they can edit their information.
",
102 | ) );
103 |
104 | $screen->add_help_tab( array(
105 | 'title' => 'Attendees List',
106 | 'id' => 'tix-attendees-list',
107 | 'content' => "
108 | Attendees List
109 | You can create a list of attendees on any page by using the [camptix_attendees] shortcode. This will create a list of avatars, names, URLs and Twitter handles if provided by the attendees. You can style the list with CSS, each item is fairly easy to target with selectors. You can even change the number of columns by adding a columns attribute, for example [camptix_attendees columns="2"].
",
110 | ) );
111 |
112 | } elseif ( $screen->id == 'edit-tix_coupon' || $screen->id == 'tix_coupon' ) {
113 |
114 | $screen->add_help_tab( array(
115 | 'title' => 'Overview',
116 | 'id' => 'tix-overview',
117 | 'content' => "
118 | Coupons
119 | Coupons are discount codes you can give to your attendees. The available fields are quite self-explanatory:
120 |
121 | Title - the coupon code. This is the code people will type in to get their discount. It's not case sensitive, so Coupon will work the same as COUPON or cOuPOn.
122 | Discount - can either be a fixed amount or a percentage, which will be deducted from the ticket price. If a ticket price is $10 and the discount is set to $3 or 30%, people will be able to purchase the ticket for $7.
123 | Quantity - the maximum number of times this coupon can be used. Note, that if the coupon quantity is more than one, an event attendee can purchase as much tickets as the coupon quantity will allow them to. This means they are not restricted to a single coupon usage per purchaser. If you'd like a coupon to be used only once, create a coupon and set the quantity to one.
124 | Applies to - check the tickets that should be discounted when this coupon code is used. Note that when you create a new ticket, it will not automatically be discounted by the saved coupons. You will have to add them explicitly.
125 | Availability - similar to tickets availability, defines the date period when the coupon can be used.
126 |
127 | You can save the coupon as a draft at any point, but it has to be Published in order to work.
",
128 | ) );
129 |
130 | } elseif ( $screen->id == 'ticket_page_tix_tools' ) {
131 |
132 | $screen->add_help_tab( array(
133 | 'title' => 'Summarize',
134 | 'id' => 'tix-summarize',
135 | 'content' => "
136 | Summarize
137 | Summaries is a great way to group your event attendees by any of the attributes, including all the possible ticket questions. Useful to find out which t-shirt sizes you need to order, or what type of food you need to get. You can also export summaries into CSV.
",
138 | ) );
139 |
140 | $screen->add_help_tab( array(
141 | 'title' => 'Revenue',
142 | 'id' => 'tix-revenue',
143 | 'content' => "
144 | Revenue
145 | The Revenue report shows the numbers and the pricing for each ticket sold, including discounts, etc. Compare the total revenue number to the one in your PayPal reports to make sure everything is in order.
",
146 | ) );
147 |
148 | $screen->add_help_tab( array(
149 | 'title' => 'Export',
150 | 'id' => 'tix-export',
151 | 'content' => "
152 | Export
153 | The Export tools helps you export all your attendee data into various formats.
",
154 | ) );
155 |
156 | $screen->add_help_tab( array(
157 | 'title' => 'Notify',
158 | 'id' => 'tix-export',
159 | 'content' => "
160 | Notify
161 | The Notify section lets you send e-mails targeted at specific ticket groups. Note that the e-mails will not be sent out straight away, but rather grouped into tasks, which are carried out using a cron schedule. You can monitor the status of every e-mail job in the History section.
",
162 | ) );
163 |
164 | } elseif ( $screen->id == 'tix_ticket_page_camptix_options' ) {
165 |
166 | $screen->add_help_tab( array(
167 | 'title' => 'Configuration',
168 | 'id' => 'tix-help-configuration',
169 | 'content' => "
170 | Configuration
171 | The basic configuration is done in the General section. The event name will appear in your outgoing emails, payment reports, etc. The currency drop-down sets the currency for all tickets. Please note that changing the currency does not convert the existing ticket prices, i.e. 10 USD will become 10 EUR. Also note that not all payment methods support all currencies, so make sure you pick the currency supported by the payment methods you're planning to use.
",
172 | ) );
173 |
174 | $screen->add_help_tab( array(
175 | 'title' => 'Payment',
176 | 'id' => 'tix-help-payment',
177 | 'content' => "
178 | Payment Methods
179 | You can configure the payment methods you'd like to use in the Payment section. To enable or disable a specific payment method, set its Enabled option to Yes or No respectively. Most payment methods will come with additional configuration fields. Supported currencies will be listed next to each payment method. If you'd like to change the currency, you can do so from the General tab.
180 |
181 | PayPal Express Checkout
182 | To use Express Checkout you'll need to obtain your API credentials from PayPal. Please note, that these are not your PayPal e-mail and password. Read the Creating an API Signature for more information. If you want to test your payments before going public, please refer to the PayPal Sandbox guide.
183 | ",
184 | ) );
185 |
186 | }
187 | }
188 | add_action( 'in_admin_header', 'tix_contextual_help' );
--------------------------------------------------------------------------------
/camptix.js:
--------------------------------------------------------------------------------
1 | /*
2 | * MDN Cookie Framework
3 | * https://developer.mozilla.org/en-US/docs/Web/API/document.cookie#A_little_framework.3A_a_complete_cookies_reader.2Fwriter_with_full_unicode_support
4 | * Updated: 2014-08-28
5 | */
6 | var docCookies={getItem:function(e){return decodeURIComponent(document.cookie.replace(new RegExp("(?:(?:^|.*;)\\s*"+encodeURIComponent(e).replace(/[\-\.\+\*]/g,"\\$&")+"\\s*\\=\\s*([^;]*).*$)|^.*$"),"$1"))||null},setItem:function(e,o,n,t,c,r){if(!e||/^(?:expires|max\-age|path|domain|secure)$/i.test(e))return!1;var s="";if(n)switch(n.constructor){case Number:s=1/0===n?"; expires=Fri, 31 Dec 9999 23:59:59 GMT":"; max-age="+n;break;case String:s="; expires="+n;break;case Date:s="; expires="+n.toUTCString()}return document.cookie=encodeURIComponent(e)+"="+encodeURIComponent(o)+s+(c?"; domain="+c:"")+(t?"; path="+t:"")+(r?"; secure":""),!0},removeItem:function(e,o,n){return e&&this.hasItem(e)?(document.cookie=encodeURIComponent(e)+"=; expires=Thu, 01 Jan 1970 00:00:00 GMT"+(n?"; domain="+n:"")+(o?"; path="+o:""),!0):!1},hasItem:function(e){return new RegExp("(?:^|;\\s*)"+encodeURIComponent(e).replace(/[\-\.\+\*]/g,"\\$&")+"\\s*\\=").test(document.cookie)},keys:function(){for(var e=document.cookie.replace(/((?:^|\s*;)[^\=]+)(?=;|$)|^\s*|\s*(?:\=[^;]*)?(?:\1|$)/g,"").split(/\s*(?:\=[^;]*)?;\s*/),o=0;oe;e++){var n=r(i[e]);if(n.trigger("appear",[n]),p[e]){var o=p[e].not(n);o.trigger("disappear",[o])}p[e]=n}}function n(e){i.push(e),p.push()}var i=[],o=!1,a=!1,f={interval:250,force_process:!1},u=e(window),p=[];e.expr[":"].appeared=function(r){var t=e(r);if(!t.is(":visible"))return!1;var n=u.scrollLeft(),i=u.scrollTop(),o=t.offset(),a=o.left,f=o.top;return f+t.height()i+u.height()||a+t.width()n+u.width()?!1:!0},e.fn.extend({appear:function(r){var i=e.extend({},f,r||{}),u=this.selector||this;if(!o){var p=function(){a||(a=!0,setTimeout(t,i.interval))};e(window).scroll(p).resize(p),o=!0}return i.force_process&&setTimeout(t,i.interval),n(u),e(u)}}),e.extend({force_appear:function(){return o?(t(),!0):!1}})}(function(){return"undefined"!=typeof module?require("jquery"):jQuery}());
19 |
20 | /**
21 | * CampTix Javascript
22 | *
23 | * Hopefully runs during wp_footer.
24 | */
25 | (function($){
26 | var tix = $( '#tix' );
27 | $( tix ).addClass( 'tix-js' );
28 |
29 | if ( $( tix ).hasClass( 'tix-has-dynamic-receipts' ) ) {
30 | refresh_receipt_emails = function() {
31 | var fields = $('.tix-field-email');
32 | var html = '';
33 | var previously_checked = $('[name="tix_receipt_email_js"]:checked').val();
34 | var checked = false;
35 |
36 | for ( var i = 0; i < fields.length; i++ ) {
37 | var value = fields[i].value;
38 | if ( value.length < 1 ) continue;
39 |
40 | var field = $(' container
');
41 | $(field).find('span').text(value);
42 | $(field).find('input').attr('value', value);
43 |
44 | if ( previously_checked != undefined && previously_checked == value && ! checked )
45 | checked = $(field).find('input').attr('checked','checked');
46 |
47 | html += $(field).html();
48 | }
49 |
50 | if ( html.length < 1 )
51 | html = '' + camptix_l10n.enterEmail + ' ';
52 |
53 | if ( html == $('#tix-receipt-emails-list').html() )
54 | return;
55 |
56 | $('#tix-receipt-emails-list').html(html);
57 |
58 | previously_checked = $('[name="tix_receipt_email_js"]:checked').val();
59 | if ( previously_checked == undefined || previously_checked.length < 1 )
60 | $('#tix-receipt-emails-list input:first').attr('checked','checked');
61 | };
62 |
63 | $('.tix-field-email').change(refresh_receipt_emails);
64 | $('.tix-field-email').keyup(refresh_receipt_emails);
65 | $(document).ready(refresh_receipt_emails);
66 | }
67 |
68 | /**
69 | * Automatically prepend http:// to URL fields if the user didn't.
70 | *
71 | * Some browsers will reject input like "example.org" as invalid because
72 | * it's missing the protocol. This confuses users who don't realize that
73 | * the protocol is required.
74 | */
75 | tix.find( 'input[type=url]' ).on( 'blur', function( event ) {
76 | var url = $( this ).val();
77 |
78 | if ( '' == url ) {
79 | return;
80 | }
81 |
82 | if ( url.match( '^https?:\/\/.*' ) === null ) {
83 | $( this ).val( 'http://' + url );
84 | }
85 | } );
86 |
87 | // Get a cookie object
88 | function tixGetCookie( name ) {
89 | var cookie = docCookies.getItem( name );
90 |
91 | if ( null == cookie ) {
92 | cookie = {};
93 | } else {
94 | cookie = $.parseJSON( cookie );
95 | }
96 |
97 | return cookie;
98 | }
99 |
100 | // Count unique visitors to [tickets] page
101 | // TODO: Refactor to use wpCookies instead of MDN Cookie Framework
102 | $( document ).ready( function() {
103 | if ( ! tix.length ) {
104 | return;
105 | }
106 |
107 | var cookie = tixGetCookie( 'camptix_client_stats' ),
108 | ajaxURL = camptix_l10n.ajaxURL;
109 |
110 | // Do nothing if we've already counted them
111 | if ( cookie.hasOwnProperty( 'visited_tickets_form' ) ) {
112 | return;
113 | }
114 |
115 | // If it's their first visit, bump the counter on the server and set the client cookie
116 | cookie.visited_tickets_form = true;
117 |
118 | if ( window.location.href.indexOf( 'tix_reservation_token' ) > -1 ) {
119 | ajaxURL += window.location.search;
120 | }
121 |
122 | $.post(
123 | ajaxURL,
124 | {
125 | action: 'camptix_client_stats',
126 | command: 'increment',
127 | stat: 'tickets_form_unique_visitors'
128 | },
129 |
130 | function( response ) {
131 | if ( true != response.success ) {
132 | return;
133 | }
134 |
135 | docCookies.setItem(
136 | 'camptix_client_stats',
137 | JSON.stringify( cookie ),
138 | 60 * 60 * 24 * 365
139 | );
140 | }
141 | );
142 | } );
143 |
144 | // Hide unknown attendee fields when reloading the page
145 | $( document ).ready( function() {
146 | tix.find( 'input.unknown-attendee' ).each( hide_input_rows_for_unknown_attendee );
147 | } );
148 |
149 | // Hide unknown attendee fields when checkbox is clicked
150 | tix.find( 'input.unknown-attendee' ).change( hide_input_rows_for_unknown_attendee );
151 |
152 | /**
153 | * Hide the input fields for unknown attendees
154 | */
155 | function hide_input_rows_for_unknown_attendee() {
156 | // Select core input rows. There aren't any question rows because those are removed by filter_unconfirmed_attendees_questions().
157 | var input_rows = $( this ).parents( 'table' ).find( 'tr.tix-row-first-name, tr.tix-row-last-name, tr.tix-row-email' );
158 |
159 | if ( this.checked ) {
160 | input_rows.each( function() {
161 | $( this ).addClass( 'tix-hidden' );
162 | } );
163 | } else {
164 | input_rows.each( function() {
165 | $( this ).removeClass( 'tix-hidden' );
166 | } );
167 | }
168 | }
169 |
170 | /**
171 | * Lazy load camptix_attendees avatars
172 | *
173 | * @uses jQuery appear plugin
174 | * @uses Jetpack's jquery.spin
175 | */
176 | var lazyLoad = {
177 | cache: {
178 | $document: $( document ),
179 | $attendees: $( '.tix-attendee-list' )
180 | },
181 |
182 | /**
183 | * Initialize placeholders to be lazy-loaded into avatars.
184 | */
185 | init: function() {
186 | // Dependencies
187 | if ( lazyLoad.cache.$attendees.length < 1 ||
188 | 'undefined' === typeof $.fn.appear ||
189 | 'undefined' === typeof wp ) {
190 | return;
191 | }
192 |
193 | var spinner = 'undefined' !== typeof $.fn.spin;
194 |
195 | lazyLoad.avatarTemplate = wp.template( 'tix-attendee-avatar' );
196 |
197 | lazyLoad.cache.$placeholders = lazyLoad.cache.$attendees.find( '.avatar-placeholder' );
198 |
199 | lazyLoad.cache.$placeholders.appear({ interval: 500 });
200 |
201 | lazyLoad.cache.$placeholders.one( 'appear', function(event) {
202 | var $placeholder = $( event.target );
203 |
204 | $placeholder.each(function() {
205 | if ( spinner ) {
206 | $( this ).spin( 'medium' );
207 | }
208 | lazyLoad.convertPlaceholder( $( this ) );
209 | });
210 | });
211 |
212 | // Trigger appear event for the placeholders in the initial viewport
213 | $.force_appear();
214 | },
215 |
216 | /**
217 | * Replace a placeholder with an instance of the avatar template.
218 | *
219 | * @param {Object} $placeholder
220 | */
221 | convertPlaceholder: function( $placeholder ) {
222 | var content = lazyLoad.avatarTemplate( $placeholder.data() );
223 |
224 | if ( content ) {
225 | $placeholder.after( content ).remove();
226 | }
227 | }
228 | };
229 |
230 | $( document ).ready( lazyLoad.init )
231 |
232 | }(jQuery));
233 |
234 | window.CampTixStripeData = window.CampTixStripeData || {};
235 |
236 | /**
237 | * Class for utility functions
238 | * Methods of this class are intended to be over written if needed for a customization.
239 | *
240 | * For egs, to over write `getSelectedPaymentOption`, do it like so:
241 | *
242 | * CampTixUtilities.getSelectedPaymentOption = function() {
243 | * // code for selecting payment method
244 | * }
245 | */
246 | var CampTixUtilities = new function() {
247 |
248 | /**
249 | * Gets the currently selected payment option. If a new payment options
250 | * layout is implemented, then over write this function to select proper
251 | * payment option
252 | *
253 | * @returns {*|string}
254 | */
255 | this.getSelectedPaymentOption = function() {
256 | return jQuery( '#tix [name="tix_payment_method"]' ).val() || 'stripe';
257 | }
258 | };
259 | window.CampTixUtilities = CampTixUtilities;
260 |
261 | /**
262 | * Functionality for the Stripe payment gateway.
263 | */
264 | var CampTixStripe = new function() {
265 | var self = this;
266 |
267 | self.data = CampTixStripeData;
268 | self.form = false;
269 |
270 | self.init = function() {
271 | self.form = jQuery( '#tix form' );
272 | self.form.on( 'submit', CampTixStripe.form_handler );
273 |
274 | // On a failed attendee data request, we'll have the previous stripe token
275 | if ( self.data.token ) {
276 | self.add_stripe_token_hidden_fields( self.data.token, self.data.receipt_email || '' );
277 | }
278 | };
279 |
280 | self.form_handler = function(e) {
281 | // Verify Stripe is the selected method.
282 | var method = CampTixUtilities.getSelectedPaymentOption();
283 |
284 | if ( 'stripe' !== method ) {
285 | return;
286 | }
287 |
288 | // If the form already has a Stripe token, bail.
289 | var tokenised = self.form.find('input[name="tix_stripe_token"]');
290 | if ( tokenised.length ) {
291 | return;
292 | }
293 |
294 | self.stripe_checkout();
295 |
296 | e.preventDefault();
297 | };
298 |
299 | self.stripe_checkout = function() {
300 | var emails = jQuery.uniqueSort(
301 | self.form.find('input[type="email"]')
302 | .filter( function () { return this.value.length; })
303 | .map( function() { return this.value; } )
304 | );
305 |
306 | var StripeHandler = StripeCheckout.configure({
307 | key: self.data.public_key,
308 | image: self.data.image,
309 | locale: 'auto',
310 | amount: parseInt( this.data.amount ),
311 | currency: self.data.currency,
312 | description: self.data.description,
313 | name: self.data.name,
314 | zipCode: true,
315 | email: ( emails.length === 1 ? emails[0] : '' ) || '',
316 | token: self.stripe_token_callback,
317 | });
318 |
319 | // Close the popup if they hit back.
320 | window.addEventListener('popstate', function() {
321 | StripeHandler.close();
322 | });
323 |
324 | StripeHandler.open();
325 | };
326 |
327 | self.stripe_token_callback = function( token ) {
328 | self.add_stripe_token_hidden_fields( token.id, token.receipt_email || token.email );
329 |
330 | self.form.submit();
331 | };
332 |
333 | self.add_stripe_token_hidden_fields = function( token_id, email ) {
334 | jQuery(' ').attr({
335 | type: 'hidden',
336 | id: 'tix_stripe_token',
337 | name: 'tix_stripe_token',
338 | value: token_id,
339 | }).appendTo( self.form );
340 |
341 | if ( email ) {
342 | jQuery(' ').attr({
343 | type: 'hidden',
344 | id: 'tix_stripe_receipt_email',
345 | name: 'tix_stripe_receipt_email',
346 | value: email,
347 | }).appendTo( self.form );
348 |
349 | /**
350 | * for backward compatibility. we renamed `tix_stripe_reciept_email`
351 | * to `tix_stripe_receipt_email` in 1.7, but older stripe plugin
352 | * would still be expecting `tix_stripe_reciept_email`
353 | */
354 | jQuery( ' ' ).attr({
355 | type: 'hidden',
356 | id: 'tix_stripe_reciept_email',
357 | name: 'tix_stripe_reciept_email',
358 | value: email,
359 | }).appendTo( self.form );
360 | }
361 |
362 | };
363 | };
364 |
365 | jQuery(document).ready( function($) {
366 | CampTixStripe.init()
367 | });
368 |
--------------------------------------------------------------------------------
/admin.js:
--------------------------------------------------------------------------------
1 | /**
2 | * CampTix Admin JavaScript
3 | */
4 | window.camptix = window.camptix || { models: {}, views: {}, collections: {} };
5 |
6 | (function($){
7 |
8 | camptix.template_options = {
9 | evaluate: /<#([\s\S]+?)#>/g,
10 | interpolate: /\{\{\{([\s\S]+?)\}\}\}/g,
11 | escape: /\{\{([^\}]+?)\}\}(?!\})/g,
12 | variable: 'data'
13 | };
14 |
15 | $(document).on( 'load-notify-segments.camptix', function() {
16 | var Segment = Backbone.Model.extend({
17 | defaults: {
18 | field: 'ticket',
19 | op: 'is',
20 | value: null,
21 | },
22 |
23 | initialize: function() {
24 | this.bind( 'change', this.change, this );
25 | this.trigger( 'change' );
26 |
27 | return Backbone.Model.prototype.initialize.apply( this, arguments );
28 | },
29 |
30 | change: function() {
31 | var selectedField = camptix.collections.segmentFields.findWhere({ option_value: this.get( 'field' ) }),
32 | values;
33 |
34 | // Make sure the value is a valid one for select types.
35 | if ( selectedField.get( 'type' ) == 'select' ) {
36 | values = _.pluck( selectedField.get( 'values' ), 'value' );
37 | if ( ! _.contains( values, this.get( 'value' ) ) ) {
38 | this.set( 'value', _.first( values ), { silent: true } );
39 | }
40 | }
41 | }
42 | });
43 |
44 | var SegmentView = Backbone.View.extend({
45 | className: 'tix-segment-item',
46 | events: {
47 | 'click .tix-delete-segment-condition': 'remove',
48 | 'change select': 'change',
49 | 'change input[type="text"]': 'change',
50 | },
51 |
52 | initialize: function() {
53 | this.model.bind( 'change', this.render, this );
54 | this.model.bind( 'destroy', this.remove, this );
55 |
56 | Backbone.View.prototype.initialize.apply( this, arguments );
57 | },
58 |
59 | render: function() {
60 | var selectedField = camptix.collections.segmentFields.findWhere({ option_value: this.model.get( 'field' ) })
61 |
62 | var data = {
63 | model: this.model.toJSON(),
64 | fields: camptix.collections.segmentFields.toJSON(),
65 | ops: selectedField.get( 'ops' ),
66 | type: selectedField.get( 'type' )
67 | };
68 |
69 | if ( data.type == 'select' ) {
70 | data.values = selectedField.get( 'values' );
71 | }
72 |
73 | this.template = _.template( $( '#camptix-tmpl-notify-segment-item' ).html(), null, camptix.template_options );
74 | this.$el.html( this.template( data ) );
75 | return this;
76 | },
77 |
78 | change: function() {
79 | var args = {
80 | field: this.$el.find( '.segment-field' ).val(),
81 | op: this.$el.find( '.segment-op' ).val(),
82 | value: this.$el.find( '.segment-value' ).val()
83 | };
84 |
85 | // Reset the value if the field has changed.
86 | if ( this.model.get( 'field' ) !== args.field )
87 | args.value = null;
88 |
89 | this.model.set( args );
90 | return this;
91 | },
92 |
93 | remove: function(e) {
94 | this.collection.remove( this.model );
95 | Backbone.View.prototype.remove.apply( this, arguments );
96 | e.preventDefault();
97 | return this;
98 | }
99 | });
100 |
101 | var Segments = Backbone.Collection.extend({
102 | model: Segment
103 | });
104 |
105 | var SegmentsView = Backbone.View.extend({
106 | initialize: function() {
107 | this.$query = $( '#tix-notify-segment-query' );
108 |
109 | this.collection.bind( 'add', this.addOne, this );
110 | this.collection.bind( 'add remove change', this.updateQuery, this );
111 | },
112 |
113 | render: function() {
114 | return this;
115 | },
116 |
117 | addOne: function( item ) {
118 | var view = new SegmentView({ model: item, collection: this.collection });
119 | $( '.tix-segments' ).append( view.render().el );
120 | },
121 |
122 | updateQuery: function() {
123 | this.$query.val( JSON.stringify( this.collection.toJSON() ) );
124 | },
125 | });
126 |
127 | var SegmentField = Backbone.Model.extend({
128 | defaults: {
129 | type: 'text',
130 | caption: '',
131 | option_value: '',
132 | ops: [ 'is', 'is not' ],
133 | values: []
134 | }
135 | });
136 |
137 | var SegmentFields = Backbone.Collection.extend({
138 | model: SegmentField
139 | });
140 |
141 | camptix.models.Segment = Segment;
142 | camptix.models.SegmentField = SegmentField;
143 |
144 | camptix.collections.segments = new Segments();
145 | camptix.collections.segmentFields = new SegmentFields();
146 |
147 | camptix.views.SegmentsView = new SegmentsView({ collection: camptix.collections.segments });
148 |
149 | $('.tix-add-segment-condition').on( 'click', function() {
150 | camptix.collections.segments.add( new camptix.models.Segment() );
151 | return false;
152 | });
153 | });
154 |
155 | $(document).on( 'load-questions.camptix', function() {
156 | var Question = Backbone.Model.extend({
157 | defaults: {
158 | post_id: 0,
159 | type: 'text',
160 | question: '',
161 | values: '',
162 | required: false,
163 | order: 0,
164 | json: ''
165 | }
166 | });
167 |
168 | var QuestionView = Backbone.View.extend({
169 |
170 | className: 'tix-item tix-item-sortable',
171 |
172 | events: {
173 | 'click a.tix-item-delete': 'clear',
174 | 'click a.tix-item-edit': 'edit'
175 | },
176 |
177 | initialize: function() {
178 | this.model.bind( 'change', this.render, this );
179 | this.model.bind( 'destroy', this.remove, this );
180 | },
181 |
182 | render: function() {
183 | // Update the hidden input.
184 | this.model.set( { json: '' }, { silent: true } );
185 | this.model.set( { json: JSON.stringify( this.model.toJSON() ) }, { silent: true } );
186 |
187 | this.$el.toggleClass( 'tix-item-required', !! this.model.get( 'required' ) );
188 | this.$el.data( 'tix-cid', this.model.cid );
189 |
190 | this.template = _.template( $( '#camptix-tmpl-question' ).html(), null, camptix.template_options );
191 | this.$el.html( this.template( this.model.toJSON() ) );
192 | return this;
193 | },
194 |
195 | clear: function(e) {
196 | if ( ! confirm( 'Are you sure you want to remove this question?' ) )
197 | return false;
198 |
199 | this.model.destroy();
200 | $( '.tix-ui-sortable' ).trigger( 'sortupdate' );
201 | return false;
202 | },
203 |
204 | edit: function(e) {
205 | camptix.views.NewQuestionForm.hide();
206 | camptix.views.ExistingQuestionForm.hide();
207 | camptix.views.EditQuestionForm.show( this.model );
208 | return this;
209 | }
210 | });
211 |
212 | var Questions = Backbone.Collection.extend({
213 | model: Question,
214 |
215 | initialize: function() {
216 | this.on( 'add', this._add, this );
217 | },
218 |
219 | _add: function( item ) {
220 | item.set( 'order', this.length );
221 | }
222 | });
223 |
224 | var QuestionsView = Backbone.View.extend({
225 | initialize: function() {
226 | this.collection.bind( 'add', this.addOne, this );
227 | },
228 |
229 | render: function() {
230 | return this;
231 | },
232 |
233 | addOne: function( item ) {
234 | var view = new QuestionView( { model: item } );
235 | $( '#tix-questions-container' ).append( view.render().el );
236 | }
237 | });
238 |
239 | camptix.models.Question = Question;
240 | camptix.questions = new Questions();
241 | camptix.views.QuestionsView = new QuestionsView({ collection: camptix.questions });
242 |
243 | var QuestionForm = Backbone.View.extend({
244 | template: null,
245 | data: {},
246 |
247 | initialize: function() {
248 | this.$container = $( '#tix-question-form' );
249 | this.$action = $( '#tix-add-question-action' );
250 |
251 | this.template = _.template( $( this.template ).html(), null, camptix.template_options );
252 | this.render.apply( this );
253 | return this;
254 | },
255 |
256 | render: function() {
257 | this.$el.html( this.template( this.data ) );
258 | this.hide.apply( this );
259 |
260 | this.$container.append( this.$el );
261 | return this;
262 | },
263 |
264 | show: function() {
265 | this.$action.hide();
266 | this.$el.show();
267 | return this;
268 | },
269 |
270 | hide: function() {
271 | this.$el.hide();
272 | this.$action.show();
273 | return this;
274 | }
275 | });
276 |
277 | var NewQuestionForm = QuestionForm.extend({
278 | template: '#camptix-tmpl-new-question-form',
279 |
280 | events: {
281 | 'click .tix-cancel': 'hide',
282 | 'click .tix-add': 'add'
283 | },
284 |
285 | render: function() {
286 | var that = this;
287 | QuestionForm.prototype.render.apply( this, arguments );
288 | this.$type = this.$( '#tix-add-question-type' );
289 | this.$type.on( 'change', function() { that.typeChange.apply( that ); } );
290 |
291 | this.typeChange.apply( this );
292 | return this;
293 | },
294 |
295 | add: function( e ) {
296 | var question = new camptix.models.Question();
297 |
298 | this.$( 'input, select' ).each( function() {
299 | var attr = $( this ).data( 'model-attribute' );
300 | var attr_type = $( this ).data( 'model-attribute-type' );
301 |
302 | if ( ! attr )
303 | return;
304 |
305 | var value = $( this ).val();
306 |
307 | // Special treatment for checkboxes.
308 | if ( 'checkbox' == attr_type )
309 | value = !! $( this ).prop('checked');
310 |
311 | question.set( attr, value, { silent: true } );
312 | });
313 |
314 | camptix.questions.add( question );
315 |
316 | // Clear form
317 | this.$( 'input[type="text"], select' ).val( '' );
318 | this.$( 'input[type="checkbox"]' ).prop( 'checked', false );
319 | this.typeChange.apply( this );
320 |
321 | e.preventDefault();
322 | return this;
323 | },
324 |
325 | typeChange: function() {
326 | var value = this.$type.val();
327 | var $row = this.$( '.tix-add-question-values-row' );
328 |
329 | if ( value && value.match( /radio|checkbox|select|tshirt/ ) )
330 | $row.show();
331 | else
332 | $row.hide();
333 |
334 | return this;
335 | }
336 | });
337 |
338 | var EditQuestionForm = NewQuestionForm.extend({
339 | render: function() {
340 | NewQuestionForm.prototype.render.apply( this, arguments );
341 |
342 | this.$( 'h4' ).text( 'Edit question:' );
343 | this.$( '.tix-add' ).text( 'Save Question' );
344 | return this;
345 | },
346 |
347 | show: function( question ) {
348 | this.question = question;
349 |
350 | this.$( 'input, select' ).each( function() {
351 | var attr = $( this ).data( 'model-attribute' );
352 | var attr_type = $( this ).data( 'model-attribute-type' );
353 |
354 | if ( ! attr )
355 | return;
356 |
357 | // Special treatment for checkboxes.
358 | if ( 'checkbox' == attr_type )
359 | $( this ).prop( 'checked', !! question.get( attr ) );
360 | else
361 | $( this ).val( question.get( attr ) );
362 | } );
363 |
364 | this.typeChange.apply( this );
365 | NewQuestionForm.prototype.show.apply( this, arguments );
366 | },
367 |
368 | add: function( e ) {
369 | question = this.question;
370 |
371 | this.$( 'input, select' ).each( function() {
372 | var attr = $( this ).data( 'model-attribute' );
373 | var attr_type = $( this ).data( 'model-attribute-type' );
374 | var value;
375 |
376 | if ( ! attr )
377 | return;
378 |
379 | value = $( this ).val();
380 |
381 | // Special treatment for checkboxes.
382 | if ( 'checkbox' == attr_type )
383 | value = !! $( this ).prop( 'checked' );
384 |
385 | question.set( attr, value, { silent: false } );
386 | } );
387 |
388 | delete this.question;
389 | this.hide.apply( this );
390 | e.preventDefault();
391 | return this;
392 | }
393 | });
394 |
395 | var ExistingQuestionForm = QuestionForm.extend({
396 | template: '#camptix-tmpl-existing-question-form',
397 |
398 | events: {
399 | 'click .tix-cancel': 'hide',
400 | 'click .tix-add': 'add'
401 | },
402 |
403 | initialize: function() {
404 | QuestionForm.prototype.initialize.apply( this, arguments );
405 | camptix.questions.on( 'add remove', this.update_disabled, this );
406 | },
407 |
408 | render: function() {
409 | QuestionForm.prototype.render.apply( this, arguments );
410 | this.update_disabled.apply( this );
411 | return this;
412 | },
413 |
414 | add: function( e ) {
415 | this.$( '.tix-existing-checkbox:checked' ).each( function( index, checkbox ) {
416 | var parent = $( checkbox ).parent();
417 | var question = new camptix.models.Question();
418 |
419 | $( parent ).find( 'input' ).each( function() {
420 | var attr = $(this).data( 'model-attribute' );
421 | if ( ! attr )
422 | return;
423 |
424 | question.set( attr, $( this ).val(), { silent: true } );
425 | } );
426 |
427 | // Make sure post_id and required are correct types, not integers.
428 | question.set( {
429 | post_id: parseInt( question.get( 'post_id' ), 10 ),
430 | required: !! parseInt( question.get( 'required' ), 10 )
431 | }, { silent: true } );
432 |
433 | var found = camptix.questions.where( { post_id: parseInt( question.get( 'post_id' ), 10 ) } );
434 |
435 | // Don't add duplicate existing questions.
436 | if ( 0 === found.length )
437 | camptix.questions.add( question );
438 |
439 | $( checkbox ).prop( 'checked', false );
440 | });
441 |
442 | e.preventDefault();
443 | return this;
444 | },
445 |
446 | update_disabled: function() {
447 | this.$( '.tix-existing-question' ).each( function() {
448 | var question_id = $( this ).data( 'tix-question-id' );
449 | var cb = $( this ).find( '.tix-existing-checkbox' );
450 | var found = camptix.questions.where( { post_id: parseInt( question_id, 10 ) } );
451 |
452 | $( cb ).prop( 'disabled', found.length > 0 );
453 | $( this ).toggleClass( 'tix-disabled', found.length > 0 );
454 | } );
455 |
456 | return this;
457 | }
458 | });
459 |
460 | $(document).ready(function() {
461 | camptix.views.NewQuestionForm = new NewQuestionForm();
462 | camptix.views.EditQuestionForm = new EditQuestionForm();
463 | camptix.views.ExistingQuestionForm = new ExistingQuestionForm();
464 |
465 | $( '.tix-ui-sortable' ).sortable( {
466 | items: '.tix-item-sortable',
467 | handle: '.tix-item-sort-handle',
468 | placeholder: 'tix-item-highlight'
469 | } );
470 |
471 | $( '.tix-ui-sortable' ).on( 'sortupdate', function( e, ui ) {
472 | var items = $( '.tix-ui-sortable .tix-item-sortable' );
473 | for ( var i = 0; i < items.length; i++ ) {
474 | var cid = $( items[i] ).data( 'tix-cid' );
475 | var model = camptix.questions.get( cid );
476 | model.set( 'order', i + 1 );
477 | }
478 | } );
479 |
480 | $( '#tix-add-question-new' ).on( 'click', function() {
481 | camptix.views.NewQuestionForm.show();
482 | return false;
483 | } );
484 |
485 | $( '#tix-add-question-existing' ).on( 'click', function() {
486 | camptix.views.ExistingQuestionForm.show();
487 | return false;
488 | } );
489 | });
490 | } );
491 |
492 | $(document).ready(function(){
493 |
494 | $( ".tix-date-field" ).datepicker({
495 | dateFormat: 'yy-mm-dd',
496 | firstDay: 1
497 | });
498 |
499 | // Show or hide the refunds date field in Setup > General.
500 | $('#tix-refunds-enabled-radios input').change(function() {
501 | if ( $(this).val() > 0 )
502 | $('#tix-refunds-date').show();
503 | else
504 | $('#tix-refunds-date').hide();
505 | });
506 |
507 | // Clicking on a notify shortcode in Tools > Notify inserts it into the email body.
508 | $('.tix-notify-shortcode').click(function() {
509 | var shortcode = $(this).find('code').text();
510 | $('#tix-notify-body').val( $('#tix-notify-body' ).val() + ' ' + shortcode );
511 | return false;
512 | });
513 |
514 | // Ticket availability range in tickets and coupons metabox.
515 | var tix_availability_dates = $( "#tix-date-from, #tix-date-to" ).datepicker({
516 | dateFormat: 'yy-mm-dd',
517 | firstDay: 1,
518 | onSelect: function( selectedDate ) {
519 | var option = this.id == "tix-date-from" ? "minDate" : "maxDate",
520 | instance = $( this ).data( "datepicker" ),
521 | date = $.datepicker.parseDate(
522 | instance.settings.dateFormat ||
523 | $.datepicker._defaults.dateFormat,
524 | selectedDate, instance.settings );
525 | tix_availability_dates.not( this ).datepicker( "option", option, date );
526 | }
527 | });
528 |
529 | // Coupon applies to all/none links.
530 | $( '#tix-applies-to-all' ).on( 'click', function(e) {
531 | $( '.tix-applies-to-checkbox' ).prop( 'checked', true );
532 | e.preventDefault();
533 | return false;
534 | });
535 | $( '#tix-applies-to-none' ).on( 'click', function(e) {
536 | $( '.tix-applies-to-checkbox' ).prop( 'checked', false );
537 | e.preventDefault();
538 | return false;
539 | });
540 |
541 |
542 | /*
543 | * Track Attendance addon
544 | */
545 |
546 | // Mark bulk attendance
547 | $( '#posts-filter' ).on( 'click', 'a.tix-mark-attended', function( event ) {
548 | var cellTemplate,
549 | cell = $( this ).parent(),
550 | attendeeID = $( this ).data( 'attendee-id' ),
551 | nonce = $( this ).data( 'nonce' );
552 |
553 | event.preventDefault();
554 |
555 | // Show a spinner until the AJAX call is done
556 | cellTemplate = _.template( $( '#tmpl-tix-attendance-spinner' ).html(), null, camptix.template_options );
557 | cell.html( cellTemplate( {} ) );
558 |
559 | // Send the request to mark the ticket holder as having actually attended
560 | $.post(
561 | ajaxurl,
562 |
563 | {
564 | action: 'tix_mark_as_attended',
565 | attendee_id: attendeeID,
566 | nonce: nonce
567 | },
568 |
569 | function( response ) {
570 | if ( response.hasOwnProperty( 'success' ) && true === response.success ) {
571 | cellTemplate = _.template( $( '#tmpl-tix-attendance-confirmed' ).html(), null, camptix.template_options );
572 | cell.html( cellTemplate( {} ) );
573 | } else {
574 | cellTemplate = _.template( $( '#tmpl-tix-mark-as-attended' ).html(), null, camptix.template_options );
575 | cell.html( cellTemplate( { 'attendee_id' : attendeeID, 'nonce': nonce } ) );
576 | }
577 | }
578 | );
579 | } );
580 |
581 | });
582 | }(jQuery));
--------------------------------------------------------------------------------
/addons/privacy.php:
--------------------------------------------------------------------------------
1 | __( 'CampTix Attendee Data', 'camptix' ),
28 | 'callback' => array( $this, 'attendee_personal_data_exporter' ),
29 | );
30 |
31 | return $exporters;
32 | }
33 |
34 | /**
35 | * Finds and exports personal data associated with an email address from the attendees list.
36 | *
37 | * @param string $email_address
38 | * @param int $page
39 | *
40 | * @return array
41 | */
42 | public function attendee_personal_data_exporter( $email_address, $page ) {
43 | /* @var CampTix_Plugin $camptix */
44 | global $camptix;
45 |
46 | $page = (int) $page;
47 |
48 | $data_to_export = array();
49 |
50 | /**
51 | * Filter: Modify the list of ticket buyer properties to export.
52 | *
53 | * @param array $props Associative array of properties. Key is the identifier of the data,
54 | * value is the human-readable label for the data in the export file.
55 | */
56 | $buyer_prop_to_export = apply_filters( 'camptix_privacy_buyer_props_to_export', array(
57 | 'tix_receipt_email' => __( 'Ticket Buyer E-mail Address', 'camptix' ),
58 | ) );
59 |
60 | /**
61 | * Filter: Modify the list of attendee properties to export.
62 | *
63 | * @param array $props Associative array of properties. Key is the identifier of the data,
64 | * value is the human-readable label for the data in the export file.
65 | */
66 | $attendee_prop_to_export = apply_filters( 'camptix_privacy_attendee_props_to_export', array(
67 | 'tix_first_name' => __( 'First Name', 'camptix' ),
68 | 'tix_last_name' => __( 'Last Name', 'camptix' ),
69 | 'tix_email' => __( 'E-mail Address', 'camptix' ),
70 | 'questions' => '',
71 | 'tix_private_form_submit_ip' => __( 'IP while viewing ticketed content', 'camptix' ),
72 | ) );
73 |
74 | $post_query = $this->get_attendee_posts( $email_address, $page );
75 |
76 | foreach ( (array) $post_query->posts as $post ) {
77 | $attendee_data_to_export = array();
78 |
79 | if ( $email_address === $post->tix_receipt_email ) {
80 | foreach ( $buyer_prop_to_export as $key => $label ) {
81 | $export = array();
82 |
83 | switch ( $key ) {
84 | case 'tix_receipt_email' :
85 | $value = get_post_meta( $post->ID, $key, true );
86 |
87 | if ( ! empty( $value ) ) {
88 | $export[] = array(
89 | 'name' => $label,
90 | 'value' => $value,
91 | );
92 | }
93 | break;
94 | }
95 |
96 | /**
97 | * Filter: Modify the export data for a particular ticket buyer property.
98 | *
99 | * @param array $export The export data.
100 | * @param string $key The property identifier.
101 | * @param string $label The data label in the export.
102 | * @param WP_Post $post The attendee post object.
103 | */
104 | $export = apply_filters( 'camptix_privacy_export_buyer_prop', $export, $key, $label, $post );
105 |
106 | if ( ! empty( $export ) ) {
107 | $attendee_data_to_export = array_merge( $attendee_data_to_export, $export );
108 | }
109 | }
110 | }
111 |
112 | if ( $email_address === $post->tix_email ) {
113 | foreach ( $attendee_prop_to_export as $key => $label ) {
114 | $export = array();
115 |
116 | switch ( $key ) {
117 | case 'tix_first_name' :
118 | case 'tix_last_name' :
119 | case 'tix_email' :
120 | $value = get_post_meta( $post->ID, $key, true );
121 |
122 | if ( ! empty( $value ) ) {
123 | $export[] = array(
124 | 'name' => $label,
125 | 'value' => $value,
126 | );
127 | }
128 | break;
129 | case 'questions' :
130 | $questions = $camptix->get_sorted_questions( $post->tix_ticket_id );
131 | $answers = $post->tix_questions;
132 |
133 | foreach ( $questions as $question ) {
134 | if ( isset( $answers[ $question->ID ] ) ) {
135 | $answer = $answers[ $question->ID ];
136 |
137 | if ( is_array( $answer ) ) {
138 | $answer = implode( ', ', $answer );
139 | }
140 |
141 | if ( ! empty( $answer ) ) {
142 | $export[] = array(
143 | 'name' => esc_html( apply_filters( 'the_title', $question->post_title ) ),
144 | 'value' => nl2br( esc_html( $answer ) ),
145 | );
146 | }
147 | }
148 | }
149 | break;
150 | case 'tix_private_form_submit_ip' :
151 | $values = get_post_meta( $post->ID, $key );
152 | /* translators: used between list items, there is a space after the comma */
153 | $values = implode( __( ', ', 'camptix' ), $values );
154 |
155 | if ( ! empty( $values ) ) {
156 | $export[] = array(
157 | 'name' => $label,
158 | 'value' => $values,
159 | );
160 | }
161 | break;
162 | }
163 |
164 | /**
165 | * Filter: Modify the export data for a particular attendee property.
166 | *
167 | * @param array $export The export data.
168 | * @param string $key The property identifier.
169 | * @param string $label The data label in the export.
170 | * @param WP_Post $post The attendee post object.
171 | */
172 | $export = apply_filters( 'camptix_privacy_export_attendee_prop', $export, $key, $label, $post );
173 |
174 | if ( ! empty( $export ) ) {
175 | $attendee_data_to_export = array_merge( $attendee_data_to_export, $export );
176 | }
177 | }
178 | }
179 |
180 | if ( ! empty( $attendee_data_to_export ) ) {
181 | $data_to_export[] = array(
182 | 'group_id' => 'camptix-attendee',
183 | 'group_label' => __( 'CampTix Attendee Data', 'camptix' ),
184 | 'item_id' => "camptix-attendee-{$post->ID}",
185 | 'data' => $attendee_data_to_export,
186 | );
187 | }
188 | }
189 |
190 | $done = $post_query->max_num_pages <= $page;
191 |
192 | return array(
193 | 'data' => $data_to_export,
194 | 'done' => $done,
195 | );
196 | }
197 |
198 | /**
199 | * Registers the personal data eraser for attendees.
200 | *
201 | * @param array $erasers
202 | *
203 | * @return array
204 | */
205 | public function register_personal_data_erasers( $erasers ) {
206 | $erasers['camptix-attendee'] = array(
207 | 'eraser_friendly_name' => __( 'CampTix Attendee Data', 'camptix' ),
208 | 'callback' => array( $this, 'attendee_personal_data_eraser' ),
209 | );
210 |
211 | return $erasers;
212 | }
213 |
214 | /**
215 | * Finds and erases personal data associated with an email address from the attendees list.
216 | *
217 | * @param string $email_address
218 | * @param int $page
219 | *
220 | * @return array
221 | */
222 | public function attendee_personal_data_eraser( $email_address, $page ) {
223 | /* @var CampTix_Plugin $camptix */
224 | global $camptix;
225 |
226 | $page = (int) $page;
227 | $items_removed = false;
228 | $items_retained = false;
229 | $messages = array();
230 |
231 | /**
232 | * Filter: Modify the list of ticket buyer properties to erase.
233 | *
234 | * @param array $props Associative array of properties. Key is the identifier of the data,
235 | * value is the data type that is used with the anonymizer function.
236 | */
237 | $buyer_prop_to_erase = apply_filters( 'camptix_privacy_buyer_props_to_erase', array(
238 | 'tix_receipt_email' => 'email',
239 | ) );
240 |
241 | /**
242 | * Filter: Modify the list of attendee properties to erase.
243 | *
244 | * @param array $props Associative array of properties. Key is the identifier of the data,
245 | * value is the data type that is used with the anonymizer function.
246 | */
247 | $attendee_prop_to_erase = apply_filters( 'camptix_privacy_attendee_props_to_erase', array(
248 | 'tix_first_name' => 'camptix_first_name',
249 | 'tix_last_name' => 'camptix_last_name',
250 | 'tix_email' => 'email',
251 | 'questions' => 'camptix_questions',
252 | 'tix_private_form_submit_ip' => 'ip',
253 | 'tix_privacy' => '',
254 | ) );
255 |
256 | $post_query = $this->get_attendee_posts( $email_address, $page );
257 |
258 | foreach ( (array) $post_query->posts as $post ) {
259 | /**
260 | * Filter: Toggle erasure for a particular attendee.
261 | *
262 | * By default this value is `true`, which will cause the erasure to proceed. The value can be set to a
263 | * message string explaining why the data was retained, and the erasure will be skipped.
264 | *
265 | * @param bool|string $anon_message True to erase. Any other value will skip erasure for the current attendee. Default true.
266 | * @param WP_Post $post The attendee post object.
267 | */
268 | $anon_message = apply_filters( 'camptix_privacy_erase_attendee', true, $post );
269 |
270 | if ( true !== $anon_message ) {
271 | if ( $anon_message && is_string( $anon_message ) ) {
272 | $messages[] = esc_html( $anon_message );
273 | } else {
274 | /* translators: %d: Comment ID */
275 | $messages[] = sprintf( __( 'Attendee %d contains personal data but could not be anonymized.', 'camptix' ), $post->ID );
276 | }
277 |
278 | $items_retained = true;
279 |
280 | continue;
281 | }
282 |
283 | if ( $email_address === $post->tix_receipt_email ) {
284 | foreach ( $buyer_prop_to_erase as $key => $type ) {
285 | /**
286 | * Action: Fires for each ticket buyer property in the erasure list.
287 | *
288 | * Use this to add erasure procedures for additional properties added via
289 | * the `camptix_privacy_buyer_props_to_erase` filter.
290 | *
291 | * @param string $key The property identifier.
292 | * @param string $type The data type of the property.
293 | * @param WP_Post $post The attendee post object.
294 | */
295 | do_action( 'camptix_privacy_erase_buyer_prop', $key, $type, $post );
296 |
297 | switch ( $key ) {
298 | case 'tix_receipt_email' :
299 | $anonymized_value = wp_privacy_anonymize_data( $type );
300 | update_post_meta( $post->ID, $key, $anonymized_value );
301 | break;
302 | }
303 | }
304 |
305 | $items_removed = true;
306 | }
307 |
308 | if ( $email_address === $post->tix_email ) {
309 | foreach ( $attendee_prop_to_erase as $key => $type ) {
310 | /**
311 | * Action: Fires for each attendee property in the erasure list.
312 | *
313 | * Use this to add erasure procedures for additional properties added via
314 | * the `camptix_privacy_attendee_props_to_erase` filter.
315 | *
316 | * @param string $key The property identifier.
317 | * @param string $type The data type of the property.
318 | * @param WP_Post $post The attendee post object.
319 | */
320 | do_action( 'camptix_privacy_erase_attendee_prop', $key, $type, $post );
321 |
322 | switch ( $key ) {
323 | case 'tix_first_name' :
324 | case 'tix_last_name' :
325 | case 'tix_email' :
326 | $anonymized_value = wp_privacy_anonymize_data( $type );
327 | update_post_meta( $post->ID, $key, $anonymized_value );
328 | break;
329 | case 'questions' :
330 | $questions = $camptix->get_sorted_questions( $post->tix_ticket_id );
331 | $answers = $post->tix_questions;
332 |
333 | $anonymized_answers = array();
334 |
335 | foreach ( $questions as $question ) {
336 | if ( isset( $answers[ $question->ID ] ) ) {
337 | /**
338 | * Filter: Toggle whether to erase the answer data for a particular question.
339 | *
340 | * @param bool $erase Set to false to retain the answer data.
341 | * @param WP_Post $question The question in question.
342 | */
343 | $erase = apply_filters( 'camptix_privacy_erase_attendee_question', true, $question );
344 |
345 | if ( false !== $erase ) {
346 | $type = 'camptix_question_' . $question->tix_type;
347 | $anonymized_answers[ $question->ID ] = wp_privacy_anonymize_data( $type );
348 | }
349 | }
350 | }
351 |
352 | update_post_meta( $post->ID, 'tix_questions', $anonymized_answers );
353 | break;
354 | case 'tix_private_form_submit_ip' :
355 | $values = get_post_meta( $post->ID, $key );
356 | $prev = '';
357 |
358 | foreach ( $values as $value ) {
359 | update_post_meta( $post->ID, $key, wp_privacy_anonymize_ip( $value ), $prev );
360 | $prev = $value;
361 | }
362 | break;
363 | case 'tix_privacy':
364 | // Set the attendee to be hidden from the public Attendees page
365 | update_post_meta( $post->ID, $key, 'private' );
366 | break;
367 | }
368 | }
369 |
370 | $items_removed = true;
371 | }
372 |
373 | // Trigger the CampTix actions for attendee posts.
374 | // This resets the post title and post content based on certain postmeta values.
375 | do_action( 'save_post', $post->ID, $post, true );
376 | }
377 |
378 | $done = $post_query->max_num_pages <= $page;
379 |
380 | return array(
381 | 'items_removed' => $items_removed,
382 | 'items_retained' => $items_retained,
383 | 'messages' => $messages,
384 | 'done' => $done,
385 | );
386 | }
387 |
388 | /**
389 | * Get the list of attendee posts related to a particular email address.
390 | *
391 | * @param string $email_address
392 | * @param int $page
393 | *
394 | * @return WP_Query
395 | */
396 | private function get_attendee_posts( $email_address, $page ) {
397 | $number = 20;
398 |
399 | return new WP_Query(
400 | array(
401 | 'posts_per_page' => $number,
402 | 'paged' => $page,
403 | 'post_type' => 'tix_attendee',
404 | 'post_status' => 'any',
405 | 'orderby' => 'ID',
406 | 'order' => 'ASC',
407 | 'meta_query' => array(
408 | 'relation' => 'OR',
409 | array(
410 | 'key' => 'tix_email',
411 | 'value' => $email_address,
412 | ),
413 | array(
414 | 'key' => 'tix_receipt_email',
415 | 'value' => $email_address,
416 | ),
417 | ),
418 | )
419 | );
420 | }
421 |
422 | /**
423 | * Handle custom data types for anonymization.
424 | *
425 | * @param string $anonymous
426 | * @param string $type
427 | * @param mixed $data
428 | *
429 | * @return mixed
430 | */
431 | public function data_anonymizers( $anonymous, $type, $data ) {
432 | switch ( $type ) {
433 | case 'camptix_full_name' :
434 | $anonymous = __( 'Anonymous', 'camptix' );
435 | break;
436 | case 'camptix_first_name' :
437 | $anonymous = __( 'Anonymous', 'camptix' );
438 | break;
439 | case 'camptix_last_name' :
440 | $anonymous = '';
441 | break;
442 | case 'camptix_question_text' :
443 | case 'camptix_question_textarea' :
444 | $anonymous = wp_privacy_anonymize_data( 'text' );
445 | break;
446 | case 'camptix_question_url' :
447 | $anonymous = wp_privacy_anonymize_data( 'url' );
448 | break;
449 | }
450 |
451 | return $anonymous;
452 | }
453 |
454 | /**
455 | * Suggested content additions for a privacy policy.
456 | *
457 | * @return void
458 | */
459 | public function add_privacy_policy_content() {
460 | if ( ! function_exists( 'wp_add_privacy_policy_content' ) ) {
461 | return;
462 | }
463 |
464 | $content = array();
465 |
466 | $content[] = '' .
467 | __( 'This sample language includes the basics around what personal data your CampTix instance may be collecting, storing and sharing, as well as who may have access to that data. Depending on what settings are enabled and which additional plugins are used, the specific information used by your CampTix instance will vary. We recommend consulting with a lawyer when deciding what information to disclose on your privacy policy.', 'camptix' ) .
468 | '
';
469 |
470 | $content[] = '' .
471 | __( 'What personal data we collect and why we collect it', 'camptix' ) .
472 | ' ';
473 |
474 | $content[] = __( 'When you register for one of our events, we’ll ask you to provide information including your name and email address. We may also ask for additional information necessary for a specific event, such as home address, phone number, meal preference, t-shirt size, agreement to the code of conduct, areas of interest, and/or interest in attending associate events. We may use this information to:', 'camptix' );
475 |
476 | $content[] = '' .
477 | '' . __( 'Send you information about your ticket and the event', 'camptix' ) . ' ' .
478 | '' . __( 'Respond to your requests, including refunds and complaints', 'camptix' ) . ' ' .
479 | '' . __( 'Process your payments and prevent fraud', 'camptix' ) . ' ' .
480 | '' . __( 'Comply with any legal obligations we have, such as calculating taxes', 'camptix' ) . ' ' .
481 | '' . __( 'Send you updates about the ticketed event and other associated events, if you choose to receive them', 'camptix' ) . ' ' .
482 | ' ';
483 |
484 | $content[] = '' .
485 | __( 'Cookies', 'camptix' ) .
486 | ' ';
487 |
488 | $content[] = __( 'We use cookies to keep track of the number of unique visitors to the Tickets page, and for managing access to content on the site that is restricted to ticket holders.', 'camptix' );
489 |
490 | $content[] = '' .
491 | __( 'Who has access', 'camptix' ) .
492 | ' ';
493 |
494 | $content[] = __( 'Members of our team have access to the information you provide us. For example, all Event Organizers can access:', 'camptix' );
495 |
496 | $content[] = '' .
497 | '' . __( 'Registration information such as which tickets were purchased and when they were purchased', 'camptix' ) . ' ' .
498 | '' . __( 'Attendee information like your name, email address, and other relevant event attendance details', 'camptix' ) . ' ' .
499 | ' ';
500 |
501 | $content[] = __( 'Our team members have access to this information to help organize the event, process refunds and support you.', 'camptix' );
502 |
503 | $content[] = '' .
504 | __( 'What we share with others', 'camptix' ) .
505 | ' ';
506 |
507 | $content[] = '' .
508 | __( 'In this section you should list who you’re sharing data with, and for what purpose. This could include, but may not be limited to, analytics, marketing, payment gateways, shipping providers, and third party embeds.', 'camptix' ) .
509 | '
';
510 |
511 | $content[] = '' .
512 | __( 'Payments', 'camptix' ) .
513 | ' ';
514 |
515 | $content[] = '' .
516 | __( 'In this subsection you should list which third party payment processors you’re using to take payments on your store since these may handle customer data. We’ve included PayPal as an example, but you should remove this if you’re not using PayPal.', 'camptix' ) .
517 | '
';
518 |
519 | $content[] = __( 'We accept payments through PayPal. When processing payments, some of your data will be passed to PayPal, including information required to process or support the payment, such as the purchase total and billing information.', 'camptix' );
520 |
521 | $content[] = sprintf(
522 | wp_kses( __( 'Please see the PayPal Privacy Policy for more details.', 'camptix' ), array( 'a' => array( 'href' => true ) ) ),
523 | 'https://www.paypal.com/us/webapps/mpp/ua/privacy-full'
524 | );
525 |
526 | $content = implode( "\n\n", $content );
527 |
528 | wp_add_privacy_policy_content(
529 | 'CampTix',
530 | wp_kses_post( wpautop( $content, false ) )
531 | );
532 | }
533 | }
534 |
535 | camptix_register_addon( 'CampTix_Addon_Privacy' );
536 |
--------------------------------------------------------------------------------
/inc/class-camptix-currencies.php:
--------------------------------------------------------------------------------
1 | array(
21 | 'label' => __( 'United Arab Emirates Dirham', 'camptix' ),
22 | 'format' => '%s AED',
23 | 'decimal_point' => 2,
24 | ),
25 | 'AFN' => array(
26 | 'label' => __( 'Afghan Afghani', 'camptix' ),
27 | 'format' => 'AFN %s',
28 | 'decimal_point' => 2,
29 | ),
30 | 'ALL' => array(
31 | 'label' => __( 'Albanian Lek', 'camptix' ),
32 | 'format' => 'L %s',
33 | 'decimal_point' => 2,
34 | ),
35 | 'AMD' => array(
36 | 'label' => __( 'Armenian Dram', 'camptix' ),
37 | 'format' => 'AMD %s',
38 | 'decimal_point' => 2,
39 | ),
40 | 'ANG' => array(
41 | 'label' => __( 'Netherlands Antillean Guilder', 'camptix' ),
42 | 'format' => 'ANG %s',
43 | 'decimal_point' => 2,
44 | ),
45 | 'AOA' => array(
46 | 'label' => __( 'Angolan Kwanza', 'camptix' ),
47 | 'format' => 'Kz %s',
48 | 'decimal_point' => 2,
49 | ),
50 | 'ARS' => array(
51 | 'label' => __( 'Argentine Peso', 'camptix' ),
52 | 'format' => 'ARS %s',
53 | 'decimal_point' => 2,
54 | ),
55 | 'AUD' => array(
56 | 'label' => __( 'Australian Dollar', 'camptix' ),
57 | 'locale' => 'en_AU.UTF-8',
58 | 'decimal_point' => 2,
59 | ),
60 | 'AWG' => array(
61 | 'label' => __( 'Aruban Florin', 'camptix' ),
62 | 'format' => 'AWG %s',
63 | 'decimal_point' => 2,
64 | ),
65 | 'AZN' => array(
66 | 'label' => __( 'Azerbaijan Manat', 'camptix' ),
67 | 'format' => 'AZN %s',
68 | 'decimal_point' => 2,
69 | ),
70 | 'BAM' => array(
71 | 'label' => __( 'Convertible Mark', 'camptix' ),
72 | 'format' => 'BAM %s',
73 | 'decimal_point' => 2,
74 | ),
75 | 'BBD' => array(
76 | 'label' => __( 'Barbados Dollar', 'camptix' ),
77 | 'format' => 'BBD %s',
78 | 'decimal_point' => 2,
79 | ),
80 | 'BDT' => array(
81 | 'label' => __( 'Taka', 'camptix' ),
82 | 'format' => 'BDT %s',
83 | 'decimal_point' => 2,
84 | ),
85 | 'BGN' => array(
86 | 'label' => __( 'Bulgarian Lev', 'camptix' ),
87 | 'format' => 'BGN %s',
88 | 'decimal_point' => 2,
89 | ),
90 | 'BIF' => array(
91 | 'label' => __( 'Burundi Franc', 'camptix' ),
92 | 'format' => 'BIF %s',
93 | 'decimal_point' => 0,
94 | ),
95 | 'BMD' => array(
96 | 'label' => __( 'Bermudian Dollar', 'camptix' ),
97 | 'format' => 'BMD %s',
98 | 'decimal_point' => 2,
99 | ),
100 | 'BND' => array(
101 | 'label' => __( 'Brunei Dollar', 'camptix' ),
102 | 'format' => 'BND %s',
103 | 'decimal_point' => 2,
104 | ),
105 | 'BOB' => array(
106 | 'label' => __( 'Boliviano', 'camptix' ),
107 | 'format' => 'BOB %s',
108 | 'decimal_point' => 2,
109 | ),
110 | 'BRL' => array(
111 | 'label' => __( 'Brazilian Real', 'camptix' ),
112 | 'locale' => 'pt_BR.UTF-8',
113 | 'decimal_point' => 2,
114 | ),
115 | 'BSD' => array(
116 | 'label' => __( 'Bahamian Dollar', 'camptix' ),
117 | 'format' => 'BSD %s',
118 | 'decimal_point' => 2,
119 | ),
120 | 'BWP' => array(
121 | 'label' => __( 'Pula', 'camptix' ),
122 | 'format' => 'BWP %s',
123 | 'decimal_point' => 2,
124 | ),
125 | 'BZD' => array(
126 | 'label' => __( 'Belize Dollar', 'camptix' ),
127 | 'format' => 'BZD %s',
128 | 'decimal_point' => 2,
129 | ),
130 | 'CAD' => array(
131 | 'label' => __( 'Canadian Dollar', 'camptix' ),
132 | 'locale' => 'en_CA.UTF-8',
133 | 'decimal_point' => 2,
134 | ),
135 | 'CDF' => array(
136 | 'label' => __( 'Congolese Franc', 'camptix' ),
137 | 'format' => 'CDF %s',
138 | 'decimal_point' => 2,
139 | ),
140 | 'CHF' => array(
141 | 'label' => __( 'Swiss Franc', 'camptix' ),
142 | 'locale' => 'fr_CH.UTF-8',
143 | 'decimal_point' => 2,
144 | ),
145 | 'CLP' => array(
146 | 'label' => __( 'Chilean Peso', 'camptix' ),
147 | 'format' => 'CLP %s',
148 | 'decimal_point' => 0,
149 | ),
150 | 'CNY' => array(
151 | 'label' => __( 'Yuan Renminbi', 'camptix' ),
152 | 'format' => 'CNY %s',
153 | 'decimal_point' => 2,
154 | ),
155 | 'COP' => array(
156 | 'label' => __( 'Colombian Peso', 'camptix' ),
157 | 'format' => 'COP %s',
158 | 'decimal_point' => 2,
159 | ),
160 | 'CRC' => array(
161 | 'label' => __( 'Costa Rican Colon', 'camptix' ),
162 | 'format' => 'CRC %s',
163 | 'decimal_point' => 2,
164 | ),
165 | 'CVE' => array(
166 | 'label' => __( 'Cabo Verde Escudo', 'camptix' ),
167 | 'format' => 'CVE %s',
168 | 'decimal_point' => 2,
169 | ),
170 | 'CZK' => array(
171 | 'label' => __( 'Czech Koruna', 'camptix' ),
172 | 'locale' => 'hcs_CZ.UTF-8',
173 | 'decimal_point' => 2,
174 | ),
175 | 'DJF' => array(
176 | 'label' => __( 'Djibouti Franc', 'camptix' ),
177 | 'format' => 'DJF %s',
178 | 'decimal_point' => 0,
179 | ),
180 | 'DKK' => array(
181 | 'label' => __( 'Danish Krone', 'camptix' ),
182 | 'locale' => 'da_DK.UTF-8',
183 | 'decimal_point' => 2,
184 | ),
185 | 'DOP' => array(
186 | 'label' => __( 'Dominican Peso', 'camptix' ),
187 | 'format' => 'DOP %s',
188 | 'decimal_point' => 2,
189 | ),
190 | 'DZD' => array(
191 | 'label' => __( 'Algerian Dinar', 'camptix' ),
192 | 'format' => 'DZD %s',
193 | 'decimal_point' => 2,
194 | ),
195 | 'EGP' => array(
196 | 'label' => __( 'Egyptian Pound', 'camptix' ),
197 | 'format' => 'EGP %s',
198 | 'decimal_point' => 2,
199 | ),
200 | 'ETB' => array(
201 | 'label' => __( 'Ethiopian Birr', 'camptix' ),
202 | 'format' => 'ETB %s',
203 | 'decimal_point' => 2,
204 | ),
205 | 'EUR' => array(
206 | 'label' => __( 'Euro', 'camptix' ),
207 | 'format' => '€ %s',
208 | 'decimal_point' => 2,
209 | ),
210 | 'FJD' => array(
211 | 'label' => __( 'Fiji Dollar', 'camptix' ),
212 | 'format' => 'FJD %s',
213 | 'decimal_point' => 2,
214 | ),
215 | 'FKP' => array(
216 | 'label' => __( 'Falkland Islands Pound', 'camptix' ),
217 | 'format' => 'FKP %s',
218 | 'decimal_point' => 2,
219 | ),
220 | 'GBP' => array(
221 | 'label' => __( 'Pound Sterling', 'camptix' ),
222 | 'locale' => 'en_GB.UTF-8',
223 | 'decimal_point' => 2,
224 | ),
225 | 'GEL' => array(
226 | 'label' => __( 'Lari', 'camptix' ),
227 | 'format' => 'GEL %s',
228 | 'decimal_point' => 2,
229 | ),
230 | 'GIP' => array(
231 | 'label' => __( 'Gibraltar Pound', 'camptix' ),
232 | 'format' => 'GIP %s',
233 | 'decimal_point' => 2,
234 | ),
235 | 'GMD' => array(
236 | 'label' => __( 'Dalasi', 'camptix' ),
237 | 'format' => 'GMD %s',
238 | 'decimal_point' => 2,
239 | ),
240 | 'GNF' => array(
241 | 'label' => __( 'Guinean Franc', 'camptix' ),
242 | 'format' => 'GNF %s',
243 | 'decimal_point' => 0,
244 | ),
245 | 'GTQ' => array(
246 | 'label' => __( 'Quetzal', 'camptix' ),
247 | 'format' => 'GTQ %s',
248 | 'decimal_point' => 2,
249 | ),
250 | 'GYD' => array(
251 | 'label' => __( 'Guyana Dollar', 'camptix' ),
252 | 'format' => 'GYD %s',
253 | 'decimal_point' => 2,
254 | ),
255 | 'HKD' => array(
256 | 'label' => __( 'Hong Kong Dollar', 'camptix' ),
257 | 'locale' => 'zh_HK.UTF-8',
258 | 'decimal_point' => 2,
259 | ),
260 | 'HNL' => array(
261 | 'label' => __( 'Lempira', 'camptix' ),
262 | 'format' => 'HNL %s',
263 | 'decimal_point' => 2,
264 | ),
265 | 'HRK' => array(
266 | 'label' => __( 'Kuna', 'camptix' ),
267 | 'format' => 'HRK %s',
268 | 'decimal_point' => 2,
269 | ),
270 | 'HTG' => array(
271 | 'label' => __( 'Gourde', 'camptix' ),
272 | 'format' => 'HTG %s',
273 | 'decimal_point' => 2,
274 | ),
275 | 'HUF' => array(
276 | 'label' => __( 'Hungarian Forint', 'camptix' ),
277 | 'locale' => 'hu_HU.UTF-8',
278 | 'decimal_point' => 2,
279 | ),
280 | 'IDR' => array(
281 | 'label' => __( 'Rupiah', 'camptix' ),
282 | 'format' => 'IDR %s',
283 | 'decimal_point' => 2,
284 | ),
285 | 'ILS' => array(
286 | 'label' => __( 'Israeli New Sheqel', 'camptix' ),
287 | 'locale' => 'he_IL.UTF-8',
288 | 'decimal_point' => 2,
289 | ),
290 | 'INR' => array(
291 | 'label' => __( 'Indian Rupee', 'camptix' ),
292 | 'format' => '₹ %s',
293 | 'decimal_point' => 2,
294 | ),
295 | 'ISK' => array(
296 | 'label' => __( 'Iceland Krona', 'camptix' ),
297 | 'format' => 'ISK %s',
298 | 'decimal_point' => 0,
299 | ),
300 | 'JMD' => array(
301 | 'label' => __( 'Jamaican Dollar', 'camptix' ),
302 | 'format' => 'JMD %s',
303 | 'decimal_point' => 2,
304 | ),
305 | 'JPY' => array(
306 | 'label' => __( 'Japanese Yen', 'camptix' ),
307 | 'locale' => 'ja_JP.UTF-8',
308 | 'decimal_point' => 0,
309 | ),
310 | 'KES' => array(
311 | 'label' => __( 'Kenyan Shilling', 'camptix' ),
312 | 'format' => 'KES %s',
313 | 'decimal_point' => 2,
314 | ),
315 | 'KGS' => array(
316 | 'label' => __( 'Som', 'camptix' ),
317 | 'format' => 'KGS %s',
318 | 'decimal_point' => 2,
319 | ),
320 | 'KHR' => array(
321 | 'label' => __( 'Riel', 'camptix' ),
322 | 'format' => 'KHR %s',
323 | 'decimal_point' => 2,
324 | ),
325 | 'KMF' => array(
326 | 'label' => __( 'Comorian Franc', 'camptix' ),
327 | 'format' => 'KMF %s',
328 | 'decimal_point' => 0,
329 | ),
330 | 'KRW' => array(
331 | 'label' => __( 'Won', 'camptix' ),
332 | 'format' => 'KRW %s',
333 | 'decimal_point' => 0,
334 | ),
335 | 'KYD' => array(
336 | 'label' => __( 'Cayman Islands Dollar', 'camptix' ),
337 | 'format' => 'KYD %s',
338 | 'decimal_point' => 2,
339 | ),
340 | 'KZT' => array(
341 | 'label' => __( 'Tenge', 'camptix' ),
342 | 'format' => 'KZT %s',
343 | 'decimal_point' => 2,
344 | ),
345 | 'LAK' => array(
346 | 'label' => __( 'Lao Kip', 'camptix' ),
347 | 'format' => 'LAK %s',
348 | 'decimal_point' => 2,
349 | ),
350 | 'LBP' => array(
351 | 'label' => __( 'Lebanese Pound', 'camptix' ),
352 | 'format' => 'LBP %s',
353 | 'decimal_point' => 2,
354 | ),
355 | 'LKR' => array(
356 | 'label' => __( 'Sri Lanka Rupee', 'camptix' ),
357 | 'format' => 'LKR %s',
358 | 'decimal_point' => 2,
359 | ),
360 | 'LRD' => array(
361 | 'label' => __( 'Liberian Dollar', 'camptix' ),
362 | 'format' => 'LRD %s',
363 | 'decimal_point' => 2,
364 | ),
365 | 'LSL' => array(
366 | 'label' => __( 'Loti', 'camptix' ),
367 | 'format' => 'LSL %s',
368 | 'decimal_point' => 2,
369 | ),
370 | 'MAD' => array(
371 | 'label' => __( 'Moroccan Dirham', 'camptix' ),
372 | 'format' => 'MAD %s',
373 | 'decimal_point' => 2,
374 | ),
375 | 'MDL' => array(
376 | 'label' => __( 'Moldovan Leu', 'camptix' ),
377 | 'format' => 'MDL %s',
378 | 'decimal_point' => 2,
379 | ),
380 | 'MGA' => array(
381 | 'label' => __( 'Malagasy Ariary', 'camptix' ),
382 | 'format' => 'MGA %s',
383 | 'decimal_point' => 2,
384 | ),
385 | 'MKD' => array(
386 | 'label' => __( 'Denar', 'camptix' ),
387 | 'format' => 'MKD %s',
388 | 'decimal_point' => 2,
389 | ),
390 | 'MMK' => array(
391 | 'label' => __( 'Kyat', 'camptix' ),
392 | 'format' => 'MMK %s',
393 | 'decimal_point' => 2,
394 | ),
395 | 'MNT' => array(
396 | 'label' => __( 'Tugrik', 'camptix' ),
397 | 'format' => 'MNT %s',
398 | 'decimal_point' => 2,
399 | ),
400 | 'MOP' => array(
401 | 'label' => __( 'Pataca', 'camptix' ),
402 | 'format' => 'MOP %s',
403 | 'decimal_point' => 2,
404 | ),
405 | 'MRO' => array(
406 | 'label' => __( 'Mauritanian Ouguiya', 'camptix' ),
407 | 'format' => 'MRO %s',
408 | 'decimal_point' => 2,
409 | ),
410 | 'MUR' => array(
411 | 'label' => __( 'Mauritius Rupee', 'camptix' ),
412 | 'format' => 'MUR %s',
413 | 'decimal_point' => 2,
414 | ),
415 | 'MVR' => array(
416 | 'label' => __( 'Rufiyaa', 'camptix' ),
417 | 'format' => 'MVR %s',
418 | 'decimal_point' => 2,
419 | ),
420 | 'MWK' => array(
421 | 'label' => __( 'Malawi Kwacha', 'camptix' ),
422 | 'format' => 'MWK %s',
423 | 'decimal_point' => 2,
424 | ),
425 | 'MXN' => array(
426 | 'label' => __( 'Mexican Peso', 'camptix' ),
427 | 'format' => '$ %s',
428 | 'decimal_point' => 2,
429 | ),
430 | 'MYR' => array(
431 | 'label' => __( 'Malaysian Ringgit', 'camptix' ),
432 | 'format' => 'RM %s',
433 | 'decimal_point' => 2,
434 | ),
435 | 'MZN' => array(
436 | 'label' => __( 'Mozambique Metical', 'camptix' ),
437 | 'format' => 'MZN %s',
438 | 'decimal_point' => 2,
439 | ),
440 | 'NAD' => array(
441 | 'label' => __( 'Namibia Dollar', 'camptix' ),
442 | 'format' => 'NAD %s',
443 | 'decimal_point' => 2,
444 | ),
445 | 'NGN' => array(
446 | 'label' => __( 'Naira', 'camptix' ),
447 | 'format' => 'NGN %s',
448 | 'decimal_point' => 2,
449 | ),
450 | 'NIO' => array(
451 | 'label' => __( 'Cordoba Oro', 'camptix' ),
452 | 'format' => 'NIO %s',
453 | 'decimal_point' => 2,
454 | ),
455 | 'NOK' => array(
456 | 'label' => __( 'Norwegian Krone', 'camptix' ),
457 | 'locale' => 'no_NO.UTF-8',
458 | 'decimal_point' => 2,
459 | ),
460 | 'NPR' => array(
461 | 'label' => __( 'Nepalese Rupee', 'camptix' ),
462 | 'format' => 'NPR %s',
463 | 'decimal_point' => 2,
464 | ),
465 | 'NZD' => array(
466 | 'label' => __( 'N.Z. Dollar', 'camptix' ),
467 | 'locale' => 'en_NZ.UTF-8',
468 | 'decimal_point' => 2,
469 | ),
470 | 'PAB' => array(
471 | 'label' => __( 'Balboa', 'camptix' ),
472 | 'format' => 'PAB %s',
473 | 'decimal_point' => 2,
474 | ),
475 | 'PEN' => array(
476 | 'label' => __( 'Sol', 'camptix' ),
477 | 'format' => 'PEN %s',
478 | 'decimal_point' => 2,
479 | ),
480 | 'PGK' => array(
481 | 'label' => __( 'Kina', 'camptix' ),
482 | 'format' => 'PGK %s',
483 | 'decimal_point' => 2,
484 | ),
485 | 'PHP' => array(
486 | 'label' => __( 'Philippine Peso', 'camptix' ),
487 | 'format' => '₱ %s',
488 | 'decimal_point' => 2,
489 | ),
490 | 'PKR' => array(
491 | 'label' => __( 'Pakistani Rupee', 'camptix' ),
492 | 'format' => '₨ %s',
493 | 'decimal_point' => 2,
494 | ),
495 | 'PLN' => array(
496 | 'label' => __( 'Polish Zloty', 'camptix' ),
497 | 'locale' => 'pl_PL.UTF-8',
498 | 'decimal_point' => 2,
499 | ),
500 | 'PYG' => array(
501 | 'label' => __( 'Guarani', 'camptix' ),
502 | 'format' => 'PYG %s',
503 | 'decimal_point' => 0,
504 | ),
505 | 'QAR' => array(
506 | 'label' => __( 'Qatari Rial', 'camptix' ),
507 | 'format' => 'QAR %s',
508 | 'decimal_point' => 2,
509 | ),
510 | 'RON' => array(
511 | 'label' => __( 'Romanian Leu', 'camptix' ),
512 | 'format' => 'RON %s',
513 | 'decimal_point' => 2,
514 | ),
515 | 'RSD' => array(
516 | 'label' => __( 'Serbian Dinar', 'camptix' ),
517 | 'format' => 'RSD %s',
518 | 'decimal_point' => 2,
519 | ),
520 | 'RUB' => array(
521 | 'label' => __( 'Russian Ruble', 'camptix' ),
522 | 'format' => 'RUB %s',
523 | 'decimal_point' => 2,
524 | ),
525 | 'RWF' => array(
526 | 'label' => __( 'Rwanda Franc', 'camptix' ),
527 | 'format' => 'RWF %s',
528 | 'decimal_point' => 0,
529 | ),
530 | 'SAR' => array(
531 | 'label' => __( 'Saudi Riyal', 'camptix' ),
532 | 'format' => 'SAR %s',
533 | 'decimal_point' => 2,
534 | ),
535 | 'SBD' => array(
536 | 'label' => __( 'Solomon Islands Dollar', 'camptix' ),
537 | 'format' => 'SBD %s',
538 | 'decimal_point' => 2,
539 | ),
540 | 'SCR' => array(
541 | 'label' => __( 'Seychelles Rupee', 'camptix' ),
542 | 'format' => 'SCR %s',
543 | 'decimal_point' => 2,
544 | ),
545 | 'SEK' => array(
546 | 'label' => __( 'Swedish Krona', 'camptix' ),
547 | 'locale' => 'sv_SE.UTF-8',
548 | 'decimal_point' => 2,
549 | ),
550 | 'SGD' => array(
551 | 'label' => __( 'Singapore Dollar', 'camptix' ),
552 | 'format' => '$ %s',
553 | 'decimal_point' => 2,
554 | ),
555 | 'SHP' => array(
556 | 'label' => __( 'Saint Helena Pound', 'camptix' ),
557 | 'format' => 'SHP %s',
558 | 'decimal_point' => 2,
559 | ),
560 | 'SLL' => array(
561 | 'label' => __( 'Leone', 'camptix' ),
562 | 'format' => 'SLL %s',
563 | 'decimal_point' => 2,
564 | ),
565 | 'SOS' => array(
566 | 'label' => __( 'Somali Shilling', 'camptix' ),
567 | 'format' => 'SOS %s',
568 | 'decimal_point' => 2,
569 | ),
570 | 'SRD' => array(
571 | 'label' => __( 'Surinam Dollar', 'camptix' ),
572 | 'format' => 'SRD %s',
573 | 'decimal_point' => 2,
574 | ),
575 | 'SZL' => array(
576 | 'label' => __( 'Lilangeni', 'camptix' ),
577 | 'format' => 'SZL %s',
578 | 'decimal_point' => 2,
579 | ),
580 | 'THB' => array(
581 | 'label' => __( 'Thai Baht', 'camptix' ),
582 | 'format' => '฿ %s',
583 | 'decimal_point' => 2,
584 | ),
585 | 'TJS' => array(
586 | 'label' => __( 'Somoni', 'camptix' ),
587 | 'format' => 'TJS %s',
588 | 'decimal_point' => 2,
589 | ),
590 | 'TOP' => array(
591 | 'label' => __( 'Pa’anga', 'camptix' ),
592 | 'format' => 'TOP %s',
593 | 'decimal_point' => 2,
594 | ),
595 | 'TRY' => array(
596 | 'label' => __( 'Turkish Lira', 'camptix' ),
597 | 'locale' => 'tr_TR.UTF-8',
598 | 'decimal_point' => 2,
599 | ),
600 | 'TTD' => array(
601 | 'label' => __( 'Trinidad and Tobago Dollar', 'camptix' ),
602 | 'format' => 'TTD %s',
603 | 'decimal_point' => 2,
604 | ),
605 | 'TWD' => array(
606 | 'label' => __( 'New Taiwan Dollar', 'camptix' ),
607 | 'locale' => 'zh_TW.UTF-8',
608 | 'decimal_point' => 2,
609 | ),
610 | 'TZS' => array(
611 | 'label' => __( 'Tanzanian Shilling', 'camptix' ),
612 | 'format' => 'TZS %s',
613 | 'decimal_point' => 2,
614 | ),
615 | 'UAH' => array(
616 | 'label' => __( 'Hryvnia', 'camptix' ),
617 | 'format' => 'UAH %s',
618 | 'decimal_point' => 2,
619 | ),
620 | 'UGX' => array(
621 | 'label' => __( 'Uganda Shilling', 'camptix' ),
622 | 'format' => 'UGX %s',
623 | 'decimal_point' => 0,
624 | ),
625 | 'USD' => array(
626 | 'label' => __( 'U.S. Dollar', 'camptix' ),
627 | 'locale' => 'en_US.UTF-8',
628 | 'decimal_point' => 2,
629 | ),
630 | 'UYU' => array(
631 | 'label' => __( 'Peso Uruguayo', 'camptix' ),
632 | 'format' => 'UYU %s',
633 | 'decimal_point' => 2,
634 | ),
635 | 'UZS' => array(
636 | 'label' => __( 'Uzbekistan Sum', 'camptix' ),
637 | 'format' => 'UZS %s',
638 | 'decimal_point' => 2,
639 | ),
640 | 'VND' => array(
641 | 'label' => __( 'Dong', 'camptix' ),
642 | 'format' => 'VND %s',
643 | 'decimal_point' => 0,
644 | ),
645 | 'VUV' => array(
646 | 'label' => __( 'Vatu', 'camptix' ),
647 | 'format' => 'VUV %s',
648 | 'decimal_point' => 0,
649 | ),
650 | 'WST' => array(
651 | 'label' => __( 'Tala', 'camptix' ),
652 | 'format' => 'WST %s',
653 | 'decimal_point' => 2,
654 | ),
655 | 'XAF' => array(
656 | 'label' => __( 'CFA Franc BEAC', 'camptix' ),
657 | 'format' => 'XAF %s',
658 | 'decimal_point' => 0,
659 | ),
660 | 'XCD' => array(
661 | 'label' => __( 'East Caribbean Dollar', 'camptix' ),
662 | 'format' => 'XCD %s',
663 | 'decimal_point' => 2,
664 | ),
665 | 'XOF' => array(
666 | 'label' => __( 'CFA Franc BCEAO', 'camptix' ),
667 | 'format' => 'XOF %s',
668 | 'decimal_point' => 0,
669 | ),
670 | 'XPF' => array(
671 | 'label' => __( 'CFP Franc', 'camptix' ),
672 | 'format' => 'XPF %s',
673 | 'decimal_point' => 0,
674 | ),
675 | 'YER' => array(
676 | 'label' => __( 'Yemeni Rial', 'camptix' ),
677 | 'format' => 'YER %s',
678 | 'decimal_point' => 2,
679 | ),
680 | 'ZAR' => array(
681 | 'label' => __( 'South African Rand', 'camptix' ),
682 | 'format' => 'R %s',
683 | 'decimal_point' => 2,
684 | ),
685 | 'ZMW' => array(
686 | 'label' => __( 'Zambian Kwacha', 'camptix' ),
687 | 'format' => 'ZMW %s',
688 | 'decimal_point' => 2,
689 | ),
690 | );
691 | }
692 |
693 | /**
694 | * Get the list of ISO currency codes supported by currently-enabled payment methods.
695 | *
696 | * Supported currencies are added via filter by different payment gateway addons/plugins. Addons should have
697 | * `$supported_currencies` variable defined with list of currencies that they support in ISO format.
698 | *
699 | * @return array The list of currency codes.
700 | */
701 | protected static function get_supported_currency_list() {
702 | return apply_filters( 'camptix_supported_currencies', array() );
703 | }
704 |
705 | /**
706 | * Get an associative array of currencies and their properties that are supported by currently-enabled payment gateways.
707 | *
708 | * Returns all the currencies that are supported by loaded payment addons, which are also defined
709 | * in `get_currency_list` method above.
710 | *
711 | * @return array The list of currencies, with their properties, which are currently supported.
712 | */
713 | public static function get_currencies() {
714 | // from https://stackoverflow.com/a/4260168/1845153
715 | $supported_currencies = array_intersect_key(
716 | self::get_currency_list(),
717 | array_flip( self::get_supported_currency_list() )
718 | );
719 |
720 | $currencies = apply_filters( 'camptix_currencies', $supported_currencies );
721 |
722 | return $currencies;
723 | }
724 | }
--------------------------------------------------------------------------------
/addons/shortcodes.php:
--------------------------------------------------------------------------------
1 | get_options();
29 | $interval = ( $camptix_options['archived'] ) ? 'daily' : 'hourly';
30 | wp_schedule_event( time(), $interval, 'camptix_cache_all_attendees_shortcodes' );
31 | }
32 | add_action( 'camptix_cache_all_attendees_shortcodes', array( $this, 'cache_all_attendees_shortcodes' ) );
33 | }
34 |
35 | /**
36 | * @param $message
37 | * @param int $post_id
38 | * @param null $data
39 | * @param string $module
40 | *
41 | * @return mixed
42 | */
43 | function log( $message, $post_id = 0, $data = null, $module = 'shortcode' ) {
44 | global $camptix;
45 | return $camptix->log( $message, $post_id, $data, $module );
46 | }
47 |
48 | /**
49 | * Runs when a post is saved.
50 | */
51 | function save_post( $post_id ) {
52 | // Only real attendee posts.
53 | if ( wp_is_post_revision( $post_id ) || 'tix_attendee' != get_post_type( $post_id ) )
54 | return;
55 |
56 | // Only non-draft attendees.
57 | $post = get_post( $post_id );
58 | if ( $post->post_status == 'draft' )
59 | return;
60 |
61 | // Signal to update the last modified time ( see $this->shutdown )
62 | $this->update_last_modified = true;
63 | }
64 |
65 | /**
66 | * Runs during shutdown, right before php stops execution.
67 | */
68 | function shutdown() {
69 | global $camptix;
70 |
71 | if ( ! isset( $this->update_last_modified ) || ! $this->update_last_modified )
72 | return;
73 |
74 | // Bump the last modified time if we've been told to ( see $this->save_post )
75 | $camptix->update_stats( 'last_modified', time() );
76 | }
77 |
78 | /**
79 | * Routine to preemptively cache the content for all of a site's instances of
80 | * the [camptix_attendees] shortcode.
81 | */
82 | public function cache_all_attendees_shortcodes() {
83 | // Get posts containing the `camptix_attendees` shortcode
84 | $params = array(
85 | 'post_type' => 'page',
86 | 'post_status' => 'publish',
87 | 's' => '[camptix_attendees',
88 | 'posts_per_page' => 50,
89 | 'update_post_term_cache' => false,
90 | 'update_post_meta_cache' => false,
91 | );
92 | $posts = get_posts( $params );
93 |
94 | if ( ! $posts ) {
95 | return;
96 | }
97 |
98 | $regex = get_shortcode_regex( array( 'camptix_attendees' ) );
99 |
100 | foreach ( $posts as $post ) {
101 | $matches = array();
102 |
103 | if ( ! preg_match_all( "/$regex/", $post->post_content, $matches, PREG_SET_ORDER ) ) {
104 | continue;
105 | }
106 |
107 | foreach ( $matches as $match ) {
108 | $attr = shortcode_parse_atts( $match[3] );
109 | $attr = $this->sanitize_attendees_atts( $attr );
110 |
111 | $this->get_attendees_shortcode_content( $attr );
112 | }
113 | }
114 | }
115 |
116 | /**
117 | * Callback for the [camptix_attendees] shortcode.
118 | */
119 | public function shortcode_attendees( $attr ) {
120 | // Required scripts
121 | wp_enqueue_script( 'wp-util' ); // For wp.template()
122 | if ( wp_script_is( 'jquery.spin', 'registered' ) ) {
123 | wp_enqueue_script( 'jquery.spin' ); // Enqueue Jetpack's spinner script if available
124 | }
125 | wp_enqueue_script( 'camptix' );
126 |
127 | // Only print the JS template once.
128 | if ( ! has_action( 'wp_print_footer_scripts', array( $this, 'avatar_js_template' ) ) ) {
129 | add_action( 'wp_print_footer_scripts', array( $this, 'avatar_js_template' ) );
130 | }
131 |
132 | $attr = $this->sanitize_attendees_atts( $attr );
133 |
134 | return $this->get_attendees_shortcode_content( $attr );
135 | }
136 |
137 | /**
138 | * Generate the key for a particular configuration to use when
139 | * setting or retrieving a cached value.
140 | *
141 | * @param array $attr Sanitized shortcode attributes
142 | *
143 | * @return string The cache key
144 | */
145 | protected function generate_attendees_cache_key( $attr ) {
146 | return 'camptix-attendees-' . md5( maybe_serialize( $attr ) );
147 | }
148 |
149 | /**
150 | * Get the content for an instance of the [camptix_attendees] shortcode.
151 | *
152 | * This checks for a cached version first. If none is found, it generates
153 | * the content and caches it before returning.
154 | *
155 | * @param array $attr Sanitized shortcode attributes
156 | * @param bool $force_refresh True to generate the content even if cached value is found.
157 | *
158 | * @return string Rendered shortcode content
159 | */
160 | public function get_attendees_shortcode_content( $attr, $force_refresh = false ) {
161 | global $camptix;
162 |
163 | /**
164 | * Action: Fires just before the [camptix_attendees] shortcode is rendered.
165 | *
166 | * @param array $attr The shortcode instance's attributes
167 | */
168 | do_action( 'camptix_attendees_shortcode_init', $attr );
169 |
170 | $cache_key = $this->generate_attendees_cache_key( $attr );
171 |
172 | // Cache duration. Day for active sites, month for archived sites.
173 | $camptix_options = $camptix->get_options();
174 | $cache_time = ( $camptix_options['archived'] ) ? MONTH_IN_SECONDS : DAY_IN_SECONDS;
175 |
176 | // Timestamp for last change in Camptix purchases/profile edits.
177 | $last_modified = $camptix->get_stats( 'last_modified' );
178 |
179 | // Return the cached value if nothing has changed since it was generated
180 | // Since key changed, backcompat with non-array cache values is no longer necessary
181 | if ( ! $force_refresh && false !== ( $cached = get_transient( $cache_key ) ) ) {
182 | // Allow outdated cached content on non-cronjob requests to avoid long page loads for visitors
183 | if ( $cached['time'] > $last_modified || ! defined( 'DOING_CRON' ) || ! DOING_CRON ) {
184 | return $cached['content'];
185 | }
186 | }
187 |
188 | $content = $this->render_attendees_list( $attr );
189 |
190 | set_transient(
191 | $cache_key,
192 | array(
193 | 'time' => time(),
194 | 'content' => $content,
195 | ),
196 | $cache_time
197 | );
198 |
199 | return $content;
200 | }
201 |
202 | /**
203 | * Normalize, sanitize, and validate attribute values for the [camptix_attendees] shortcode.
204 | *
205 | * @param array $attr Raw attributes
206 | *
207 | * @return array Sanitized attributes
208 | */
209 | public function sanitize_attendees_atts( $attr ) {
210 | $attr = shortcode_atts(
211 | array(
212 | 'order' => 'asc',
213 | 'orderby' => 'title',
214 | 'posts_per_page' => 10000,
215 | 'tickets' => false,
216 | 'columns' => 3,
217 | 'questions' => '',
218 | ),
219 | $attr,
220 | 'camptix_attendees'
221 | );
222 |
223 | // @todo validate atts here
224 |
225 | if ( ! in_array( strtolower( $attr['order'] ), array( 'asc', 'desc' ) ) ) {
226 | $attr['order'] = 'asc';
227 | }
228 |
229 | if ( ! in_array( strtolower( $attr['orderby'] ), array( 'title', 'date' ) ) ) {
230 | $attr['orderby'] = 'title';
231 | }
232 |
233 | if ( $attr['tickets'] ) {
234 | $attr['tickets'] = array_map( 'intval', explode( ',', $attr['tickets'] ) );
235 | }
236 |
237 | $attr['posts_per_page'] = absint( $attr['posts_per_page'] );
238 |
239 | return $attr;
240 | }
241 |
242 | /**
243 | * Render the HTML markup for an instance of [camptix_attendees].
244 | *
245 | * @param array $attr Sanitized shortcode attributes
246 | *
247 | * @return string HTML
248 | */
249 | protected function render_attendees_list( $attr ) {
250 | global $camptix;
251 |
252 | $query_args = array();
253 | if ( is_array( $attr['tickets'] ) && count( $attr['tickets'] ) > 0 ) {
254 | $query_args['meta_query'] = array(
255 | array(
256 | 'key' => 'tix_ticket_id',
257 | 'compare' => 'IN',
258 | 'value' => $attr['tickets'],
259 | )
260 | );
261 | }
262 |
263 | $questions = $this->get_questions_from_titles( $attr['questions'] );
264 |
265 | $paged = 0;
266 | $printed = 0;
267 |
268 | ob_start();
269 | ?>
270 |
271 |
272 | 'tix_attendee',
279 | 'posts_per_page' => 200,
280 | 'post_status' => array( 'publish', 'pending' ),
281 | 'paged' => $paged,
282 | 'order' => $attr['order'],
283 | 'orderby' => $attr['orderby'],
284 | 'fields' => 'ids', // ! no post objects
285 | 'cache_results' => false,
286 | ),
287 | $query_args
288 | ), $attr );
289 | $attendees = get_posts( $attendee_args );
290 |
291 | if ( ! is_array( $attendees ) || count( $attendees ) < 1 ) {
292 | break; // life saver!
293 | }
294 |
295 | // Disable object cache for prepared metadata.
296 | $camptix->filter_post_meta = $camptix->prepare_metadata_for( $attendees );
297 |
298 | foreach ( $attendees as $attendee_id ) {
299 | $attendee_answers = (array) get_post_meta( $attendee_id, 'tix_questions', true );
300 | if ( $printed >= $attr['posts_per_page'] ) {
301 | break;
302 | }
303 |
304 | // Skip attendees marked as private.
305 | $privacy = get_post_meta( $attendee_id, 'tix_privacy', true );
306 | if ( $privacy == 'private' ) {
307 | $printed ++;
308 | continue;
309 | }
310 |
311 | echo '';
312 |
313 | $first = get_post_meta( $attendee_id, 'tix_first_name', true );
314 | $last = get_post_meta( $attendee_id, 'tix_last_name', true );
315 |
316 | // Avatar placeholder
317 | echo $this->get_avatar_placeholder( get_post_meta( $attendee_id, 'tix_email', true ) );
318 | ?>
319 |
320 |
321 | format_name_string( '%first% %last% ', esc_html( $first ), esc_html( $last ) ); ?>
322 |
323 |
324 | ID ] ) ) : ?>
326 |
327 | ID ];
329 |
330 | /**
331 | * Make sure values stored as arrays are displayed as a comma separated list.
332 | */
333 | if ( is_array( $answer ) ) {
334 | /* translators: used between list items, there is a space after the comma */
335 | $answer = implode( __( ', ', 'camptix' ), $answer );
336 | }
337 |
338 | echo esc_html( $answer );
339 | ?>
340 |
341 |
342 |
343 |
344 | ';
353 |
354 | $printed ++;
355 | } // foreach
356 |
357 | $camptix->filter_post_meta = false; // cleanup
358 | } // while true
359 | ?>
360 |
361 |
362 |
363 | ',
390 | get_avatar_url( $id_or_email ),
391 | get_avatar_url( $id_or_email, array( 'size' => $size * 2 ) ),
392 | $size,
393 | ''
394 | );
395 | }
396 |
397 | /**
398 | * An Underscore.js template for the attendee avatar.
399 | */
400 | public function avatar_js_template() {
401 | ?>
402 |
412 | get_all_questions();
432 | $questions = array();
433 |
434 | foreach ( $slugs as $slug ) {
435 | $matched = wp_list_filter( $all_questions, array( 'post_name' => $slug ) );
436 |
437 | if ( ! empty( $matched ) ) {
438 | $questions = array_merge( $questions, $matched );
439 | }
440 | }
441 |
442 | return $questions;
443 | }
444 |
445 | /**
446 | * Callback for the [camptix_attendees] shortcode.
447 | */
448 | function shortcode_stats( $atts ) {
449 | global $camptix;
450 |
451 | return isset( $atts['stat'] ) ? $camptix->get_stats( $atts['stat'] ) : '';
452 | }
453 |
454 | /**
455 | * Executes during template_redirect, watches for the private
456 | * shortcode form submission, searches attendees, sets view token cookies.
457 | *
458 | * @see shortcode_private
459 | */
460 | function shortcode_private_template_redirect() {
461 | global $camptix;
462 |
463 | // Indicates this function did run, nothing more.
464 | $this->did_shortcode_private_template_redirect = 1;
465 |
466 | if ( isset( $_POST['tix_private_shortcode_submit'] ) ) {
467 | $email = isset( $_POST['tix_email'] ) ? trim( stripslashes( $_POST['tix_email'] ) ) : '';
468 |
469 | // Remove cookies if a previous one was set.
470 | if ( isset( $_COOKIE['tix_view_token'] ) ) {
471 | setcookie( 'tix_view_token', '', time() - 60*60, COOKIEPATH, COOKIE_DOMAIN, false );
472 | unset( $_COOKIE['tix_view_token'] );
473 | }
474 |
475 | if ( empty( $email ) ) {
476 | return $camptix->error( __( 'Please enter the e-mail address that was used to register for your ticket.', 'camptix' ) );
477 | }
478 |
479 | if ( ! is_email( $email ) )
480 | return $camptix->error( __( 'The e-mail address you have entered does not seem to be valid.', 'camptix' ) );
481 |
482 | $attendees = get_posts( array(
483 | 'posts_per_page' => 50, // sane enough?
484 | 'post_type' => 'tix_attendee',
485 | 'post_status' => 'publish',
486 | 'meta_query' => array(
487 | array(
488 | 'key' => 'tix_email',
489 | 'value' => $email,
490 | ),
491 | ),
492 | ) );
493 |
494 | if ( $attendees ) {
495 | $attendee = $attendees[0];
496 |
497 | $view_token = $this->generate_view_token_for_attendee( $attendee->ID );
498 | setcookie( 'tix_view_token', $view_token, time() + 60*60*48, COOKIEPATH, COOKIE_DOMAIN, false );
499 | $_COOKIE['tix_view_token'] = $view_token;
500 |
501 | foreach ( $attendees as $attendee ) {
502 | update_post_meta( $attendee->ID, 'tix_view_token', $view_token );
503 | $count = get_post_meta( $attendee->ID, 'tix_private_form_submit_count', true );
504 | if ( ! $count ) $count = 0;
505 | $count++;
506 | update_post_meta( $attendee->ID, 'tix_private_form_submit_count', $count );
507 | add_post_meta( $attendee->ID, 'tix_private_form_submit_entry', $_SERVER );
508 | add_post_meta( $attendee->ID, 'tix_private_form_submit_ip', @$_SERVER['REMOTE_ADDR'] );
509 | $this->log( sprintf( 'Viewing private content using %s', @$_SERVER['REMOTE_ADDR'] ), $attendee->ID, $_SERVER );
510 | }
511 | } else {
512 | $this->log( __( 'The information you have entered is incorrect. Please try again.', 'camptix' ) );
513 | }
514 | }
515 | }
516 |
517 | /**
518 | * [camptix_private] shortcode callback, depends on the template redirect
519 | * part to set cookies, looks for attendee by post by view token, compares
520 | * requested ticket ids and shows content or login form.
521 | *
522 | * @see shortcode_private_template_redirect
523 | * @see shortcode_private_login_form
524 | * @see shortcode_private_display_content
525 | */
526 | function shortcode_private( $atts, $content ) {
527 | global $camptix;
528 |
529 | if ( ! isset( $this->did_shortcode_private_template_redirect ) )
530 | return __( 'An error has occurred.', 'camptix' );
531 |
532 | // Lazy load the camptix js.
533 | wp_enqueue_script( 'camptix' );
534 |
535 | // Don't cache this page.
536 | if ( ! defined( 'DONOTCACHEPAGE' ) )
537 | define( 'DONOTCACHEPAGE', true );
538 |
539 | $args = shortcode_atts( array(
540 | 'ticket_ids' => null,
541 | 'logged_out_message' => '',
542 | ), $atts );
543 |
544 | $can_view_content = false;
545 | $error = false;
546 |
547 | // If we have a view token cookie, we cas use that to search for attendees.
548 | if ( isset( $_COOKIE['tix_view_token'] ) && ! empty( $_COOKIE['tix_view_token'] ) ) {
549 | $view_token = $_COOKIE['tix_view_token'];
550 | $attendees_params = apply_filters( 'camptix_private_attendees_parameters', array(
551 | 'posts_per_page' => 50, // sane?
552 | 'post_type' => 'tix_attendee',
553 | 'post_status' => 'publish',
554 | 'meta_query' => array(
555 | array(
556 | 'key' => 'tix_view_token',
557 | 'value' => $view_token,
558 | ),
559 | ),
560 | ) );
561 | $attendees = get_posts( $attendees_params );
562 |
563 | // Having attendees is one piece of the puzzle.
564 | // Making sure they have the right tickets is the other.
565 | if ( $attendees ) {
566 | $attendee = $attendees[0];
567 |
568 | // Let's try and recreate the view token and see if it was generated for this user.
569 | $expected_view_token = $this->generate_view_token_for_attendee( $attendee->ID );
570 | if ( $expected_view_token != $view_token ) {
571 | $camptix->error( __( 'Looks like you logged in from a different computer. Please log in again.', 'camptix' ) );
572 | $error = true;
573 | }
574 |
575 | /** @todo: maybe cleanup the nested ifs **/
576 | if ( ! $error ) {
577 | if ( $args['ticket_ids'] )
578 | $args['ticket_ids'] = array_map( 'intval', explode( ',', $args['ticket_ids'] ) );
579 | else
580 | $can_view_content = true;
581 |
582 | // If at least one ticket is found, break.
583 | if ( $args['ticket_ids'] ) {
584 | foreach ( $attendees as $attendee ) {
585 | if ( in_array( get_post_meta( $attendee->ID, 'tix_ticket_id', true ), $args['ticket_ids'] ) ) {
586 | $can_view_content = true;
587 | break;
588 | }
589 | }
590 | }
591 |
592 | if ( ! $can_view_content && isset( $_POST['tix_private_shortcode_submit'] ) ) {
593 | $camptix->error( __( 'Sorry, but your ticket does not allow you to view this content.', 'camptix' ) );
594 | }
595 | }
596 |
597 | } else {
598 | if ( isset( $_POST['tix_private_shortcode_submit'] ) )
599 | $camptix->error( __( 'Sorry, but your ticket does not allow you to view this content.', 'camptix' ) );
600 | }
601 | }
602 |
603 | if ( $can_view_content && $attendee ) {
604 | if ( isset( $_POST['tix_private_shortcode_submit'] ) )
605 | $camptix->info( __( 'Success! Enjoy your content!', 'camptix' ) );
606 |
607 | return $this->shortcode_private_display_content( $args, $content );
608 | } else {
609 | if ( ! isset( $_POST['tix_private_shortcode_submit'] ) && ! $error )
610 | $camptix->notice( __( 'The content on this page is private. Please log in using the form below.', 'camptix' ) );
611 |
612 | return $this->shortcode_private_login_form( $args, $content );
613 | }
614 | }
615 |
616 | /**
617 | * [camptix_private] shortcode, displays the login form.
618 | */
619 | function shortcode_private_login_form( $atts, $content ) {
620 | $email = isset( $_POST['tix_email'] ) ? $_POST['tix_email'] : '';
621 | ob_start();
622 |
623 | // @todo Note that in order to include HTML markup in the logged out message, the shortcode
624 | // attribute needs to enclose the value in single instead of double quotes. TinyMCE enforces
625 | // double quotes on HTML attributes, which will break the shortcode if it also uses double quotes.
626 | if ( ! empty( $atts['logged_out_message'] ) ) {
627 | echo wpautop( $atts['logged_out_message'] );
628 | }
629 |
630 | ?>
631 |
632 |
652 | ';
675 | do_action( 'camptix_notices' );
676 |
677 | echo do_shortcode( $content );
678 |
679 | echo '';
680 | $content = ob_get_contents();
681 | ob_end_clean();
682 | return $content;
683 | }
684 |
685 | function generate_view_token_for_attendee( $attendee_id ) {
686 | $email = get_post_meta( $attendee_id, 'tix_email', true );
687 | $ip = isset( $_SERVER['REMOTE_ADDR'] ) ? $_SERVER['REMOTE_ADDR'] : '';
688 |
689 | $view_token = md5( 'tix-view-token-' . strtolower( $email . $ip ) );
690 | return $view_token;
691 | }
692 | }
693 |
694 | // Register this class as a CampTix Addon.
695 | camptix_register_addon( 'CampTix_Addon_Shortcodes' );
696 |
--------------------------------------------------------------------------------
/addons/payment-stripe.php:
--------------------------------------------------------------------------------
1 | true,
30 | 'refund-all' => true,
31 | );
32 |
33 | /**
34 | * We can have an array to store our options.
35 | * Use `$this->get_payment_options()` to retrieve them.
36 | */
37 | protected $options = array();
38 |
39 | /**
40 | * Runs during camptix_init, loads our options and sets some actions.
41 | *
42 | * @see CampTix_Addon
43 | */
44 | public function camptix_init() {
45 | $this->options = array_merge(
46 | array(
47 | 'api_predef' => '',
48 | 'api_secret_key' => '',
49 | 'api_public_key' => '',
50 | 'api_test_secret_key' => '',
51 | 'api_test_public_key' => '',
52 | 'sandbox' => true,
53 | ),
54 | $this->get_payment_options()
55 | );
56 |
57 | add_action( 'camptix_form_attendee_info_before', array( $this, 'camptix_form_attendee_info_before' ), 10, 2 );
58 | add_filter( 'camptix_payment_result', array( $this, 'camptix_payment_result' ), 10, 3 );
59 | }
60 |
61 | /**
62 | * Get the credentials for the API account.
63 | *
64 | * If a standard account is setup, this will just use the value that's
65 | * already in $this->options. If a predefined account is setup, though, it
66 | * will use those instead.
67 | *
68 | * SECURITY WARNING: This must be called on the fly, and saved in a local
69 | * variable instead of $this->options. Storing the predef credentials in
70 | * $this->options would result in them being exposed to the user if they
71 | * switched from a predefined account to a standard one. That happens because
72 | * validate_options() will not strip the predefined credentials when options
73 | * are saved in this scenario, so they would be saved to the database.
74 | *
75 | * validate_options() could be updated to protect against that, but that's
76 | * more susceptible to human error. It's simpler, and therefore safer, to
77 | * just never let predefined credentials into $this->options to begin with.
78 | *
79 | * @return array
80 | */
81 | public function get_api_credentials() {
82 | $options = array_merge( $this->options, $this->get_predefined_account( $this->options['api_predef'] ) );
83 |
84 | $prefix = 'api_';
85 | if ( true === $options['sandbox'] ) {
86 | $prefix = 'api_test_';
87 | }
88 |
89 | return array(
90 | 'api_public_key' => $options[ $prefix . 'public_key' ],
91 | 'api_secret_key' => $options[ $prefix . 'secret_key' ],
92 | );
93 | }
94 |
95 | /**
96 | * Set up the data for Stripe and enqueue the assets.
97 | *
98 | * @param array $order Data about the current order.
99 | * @param array $options CampTix options.
100 | */
101 | public function camptix_form_attendee_info_before( $order, $options ) {
102 | if ( ! $order['total'] ) {
103 | return;
104 | }
105 |
106 | $credentials = $this->get_api_credentials();
107 |
108 | $item_summary = array();
109 | foreach ( $order['items'] as $item ) {
110 | $item_summary[] = sprintf(
111 | /* translators: 1: Name of ticket; 2: Quantity of ticket; */
112 | __( '%1$s x %2$d', 'camptix' ),
113 | esc_js( $item['name'] ),
114 | absint( $item['quantity'] )
115 | );
116 | }
117 |
118 | /* translators: used between list items, there is a space after the comma */
119 | $description = implode( __( ', ', 'camptix' ), $item_summary );
120 |
121 | wp_enqueue_script(
122 | 'stripe-checkout',
123 | 'https://checkout.stripe.com/checkout.js',
124 | array(),
125 | false,
126 | true
127 | );
128 |
129 | try {
130 | $amount = $this->get_fractional_unit_amount( $options['currency'], $order['total'] );
131 | } catch ( Exception $exception ) {
132 | $amount = null;
133 | }
134 |
135 | /**
136 | * Filter: Modify the URL of the image used for the Stripe checkout overlay.
137 | *
138 | * By default, the Site Icon URL will be used for this image if one is available.
139 | *
140 | * @param string $checkout_image_url
141 | */
142 | $checkout_image_url = apply_filters( 'camptix_stripe_checkout_image_url', get_site_icon_url() );
143 |
144 | wp_localize_script( 'stripe-checkout', 'CampTixStripeData', array(
145 | 'public_key' => $credentials['api_public_key'],
146 | 'name' => $options['event_name'],
147 | 'image' => ( $checkout_image_url ) ? esc_url( $checkout_image_url ) : '',
148 | 'description' => trim( $description ),
149 | 'amount' => $amount,
150 | 'currency' => $options['currency'],
151 | 'token' => ! empty( $_POST['tix_stripe_token'] ) ? wp_unslash( $_POST['tix_stripe_token'] ) : '',
152 | 'receipt_email' => ! empty( $_POST['tix_stripe_receipt_email'] ) ? wp_unslash( $_POST['tix_stripe_receipt_email'] ) : '',
153 | ) );
154 | }
155 |
156 | /**
157 | * Convert an amount in the currency's base unit to its equivalent fractional unit.
158 | *
159 | * Stripe wants amounts in the fractional unit (e.g., pennies), not the base unit (e.g., dollars).
160 | *
161 | * The data here comes from https://stripe.com/docs/currencies
162 | *
163 | * @param string $order_currency
164 | * @param int $base_unit_amount
165 | *
166 | * @return int
167 | * @throws Exception
168 | */
169 | public function get_fractional_unit_amount( $order_currency, $base_unit_amount ) {
170 | $fractional_amount = null;
171 |
172 | $currency_multipliers = array(
173 | // Zero-decimal currencies
174 | 1 => array(
175 | 'BIF', 'CLP', 'DJF', 'GNF', 'JPY', 'KMF', 'KRW', 'MGA', 'PYG', 'RWF', 'UGX', 'VND', 'VUV', 'XAF',
176 | 'XOF', 'XPF',
177 | ),
178 | 100 => array(
179 | 'AED', 'AFN', 'ALL', 'AMD', 'ANG', 'AOA', 'ARS', 'AUD', 'AWG', 'AZN', 'BAM', 'BBD', 'BDT', 'BGN',
180 | 'BMD', 'BND', 'BOB', 'BRL', 'BSD', 'BWP', 'BZD', 'CAD', 'CDF', 'CHF', 'CNY', 'COP',
181 | 'CRC', 'CVE', 'CZK', 'DKK', 'DOP', 'DZD', 'EGP', 'ETB', 'EUR', 'FJD', 'FKP',
182 | 'GBP', 'GEL', 'GIP', 'GMD', 'GTQ', 'GYD', 'HKD', 'HNL', 'HRK', 'HTG', 'HUF', 'IDR',
183 | 'ILS', 'INR', 'ISK', 'JMD', 'KES', 'KGS', 'KHR', 'KYD', 'KZT',
184 | 'LAK', 'LBP', 'LKR', 'LRD', 'LSL', 'MAD', 'MDL', 'MKD', 'MMK', 'MNT', 'MRO', 'MOP', 'MUR', 'MVR', 'MWK',
185 | 'MXN', 'MYR', 'MZN', 'NAD', 'NGN', 'NIO', 'NOK', 'NPR', 'NZD', 'PAB', 'PEN', 'PGK', 'PHP', 'PKR',
186 | 'PLN', 'QAR', 'RON', 'RSD', 'RUB', 'SAR', 'SBD', 'SCR', 'SEK', 'SGD', 'SHP', 'SLL',
187 | 'SOS', 'SRD', 'STD', 'SZL', 'THB', 'TJS', 'TOP', 'TRY', 'TTD', 'TWD',
188 | 'TZS', 'UAH', 'USD', 'UYU', 'UZS', 'WST', 'XCD', 'YER', 'ZAR', 'ZMW',
189 | ),
190 | );
191 |
192 | foreach ( $currency_multipliers as $multiplier => $currencies ) {
193 | if ( in_array( $order_currency, $currencies, true ) ) {
194 | $fractional_amount = floatval( $base_unit_amount ) * $multiplier;
195 | }
196 | }
197 |
198 | if ( is_null( $fractional_amount ) ) {
199 | throw new Exception( "Unknown currency multiplier for $order_currency." );
200 | }
201 |
202 | return intval( $fractional_amount );
203 | }
204 |
205 | /**
206 | * Add payment settings fields
207 | *
208 | * This runs during settings field registration in CampTix for the
209 | * payment methods configuration screen. If your payment method has
210 | * options, this method is the place to add them to. You can use the
211 | * helper function to add typical settings fields. Don't forget to
212 | * validate them all in validate_options.
213 | */
214 | public function payment_settings_fields() {
215 | // Allow pre-defined accounts if any are defined by plugins.
216 | if ( count( $this->get_predefined_accounts() ) > 0 ) {
217 | $this->add_settings_field_helper( 'api_predef', __( 'Predefined Account', 'camptix' ), array( $this, 'field_api_predef' ) );
218 | }
219 |
220 | // Settings fields are not needed when a predefined account is chosen.
221 | // These settings fields should *never* expose predefined credentials.
222 | if ( ! $this->get_predefined_account() ) {
223 | $this->add_settings_field_helper( 'api_secret_key', __( 'Secret Key', 'camptix' ), array( $this, 'field_text' ) );
224 | $this->add_settings_field_helper( 'api_public_key', __( 'Publishable Key', 'camptix' ), array( $this, 'field_text' ) );
225 | $this->add_settings_field_helper( 'api_test_secret_key', __( 'Test Secret Key', 'camptix' ), array( $this, 'field_text' ) );
226 | $this->add_settings_field_helper( 'api_test_public_key', __( 'Test Publishable Key', 'camptix' ), array( $this, 'field_text' ) );
227 | $this->add_settings_field_helper( 'sandbox', __( 'Sandbox Mode', 'camptix' ), array( $this, 'field_yesno' ),
228 | sprintf(
229 | __( 'When Sandbox Mode is enabled, the Test keys will be used for transactions. Read more about testing transactions with Stripe.', 'camptix' ),
230 | 'https://stripe.com/docs/testing'
231 | )
232 | );
233 | }
234 | }
235 |
236 | /**
237 | * Predefined accounts field callback
238 | *
239 | * Renders a drop-down select with a list of predefined accounts
240 | * to select from, as well as some js for better ux.
241 | *
242 | * @uses $this->get_predefined_accounts()
243 | *
244 | * @param array $args
245 | */
246 | public function field_api_predef( $args ) {
247 | $accounts = $this->get_predefined_accounts();
248 |
249 | if ( empty( $accounts ) ) {
250 | return;
251 | }
252 |
253 | ?>
254 |
255 |
256 |
257 |
258 | $account ) : ?>
259 | >
260 |
261 |
262 |
263 |
264 |
265 |
266 |
283 |
284 | get_predefined_accounts();
321 |
322 | if ( false === $key ) {
323 | $key = $this->options['api_predef'];
324 | }
325 |
326 | if ( ! array_key_exists( $key, $accounts ) ) {
327 | return array();
328 | }
329 |
330 | return $accounts[ $key ];
331 | }
332 |
333 | /**
334 | * Validate options
335 | *
336 | * @param array $input
337 | *
338 | * @return array
339 | */
340 | public function validate_options( $input ) {
341 | $output = $this->options;
342 |
343 | if ( isset( $input['api_secret_key'] ) ) {
344 | $output['api_secret_key'] = $input['api_secret_key'];
345 | }
346 |
347 | if ( isset( $input['api_test_secret_key'] ) ) {
348 | $output['api_test_secret_key'] = $input['api_test_secret_key'];
349 | }
350 |
351 | if ( isset( $input['api_public_key'] ) ) {
352 | $output['api_public_key'] = $input['api_public_key'];
353 | }
354 |
355 | if ( isset( $input['api_test_public_key'] ) ) {
356 | $output['api_test_public_key'] = $input['api_test_public_key'];
357 | }
358 |
359 | if ( isset( $input['sandbox'] ) ) {
360 | $output['sandbox'] = (bool) $input['sandbox'];
361 | }
362 |
363 | if ( isset( $input['api_predef'] ) ) {
364 | // If a valid predefined account is set, erase the credentials array.
365 | // We do not store predefined credentials in options, only code.
366 | if ( $this->get_predefined_account( $input['api_predef'] ) ) {
367 | $output = array_merge( $output, array(
368 | 'api_secret_key' => '',
369 | 'api_public_key' => '',
370 | 'api_test_secret_key' => '',
371 | 'api_test_public_key' => '',
372 | ) );
373 | } else {
374 | $input['api_predef'] = '';
375 | }
376 |
377 | $output['api_predef'] = $input['api_predef'];
378 | }
379 |
380 | return $output;
381 | }
382 |
383 | /**
384 | * Submits a single, user-initiated charge request to Stripe and returns the result.
385 | *
386 | * @param string $payment_token
387 | *
388 | * @return int One of the CampTix_Plugin::PAYMENT_STATUS_{status} constants
389 | */
390 | public function payment_checkout( $payment_token ) {
391 | if ( empty( $payment_token ) ) {
392 | return false;
393 | }
394 |
395 | /** @var CampTix_Plugin $camptix */
396 | global $camptix;
397 |
398 | if ( ! in_array( $this->camptix_options['currency'], $this->supported_currencies ) ) {
399 | wp_die( __( 'The selected currency is not supported by this payment method.', 'camptix' ) );
400 | }
401 |
402 | $order = $this->get_order( $payment_token );
403 |
404 | // One final check before charging the user.
405 | if ( ! $camptix->verify_order( $order ) ) {
406 | $camptix->log( "Could not verify order", $order['attendee_id'], array( 'payment_token' => $payment_token ), 'stripe' );
407 | wp_die( 'Something went wrong, order is not available.' );
408 | }
409 |
410 | $credentials = $this->get_api_credentials();
411 |
412 | $stripe = new CampTix_Stripe_API_Client( $payment_token, $credentials['api_secret_key'] );
413 | $amount = $this->get_fractional_unit_amount( $this->camptix_options['currency'], $order['total'] );
414 | $source = wp_unslash( $_POST['tix_stripe_token'] );
415 | $description = $this->camptix_options['event_name'];
416 | $receipt_email = isset( $_POST['tix_stripe_receipt_email'] ) ? wp_unslash( $_POST['tix_stripe_receipt_email'] ) : false;
417 | $metadata = array();
418 |
419 | foreach ( $order['items'] as $item ) {
420 | $metadata[ $item['name'] ] = $item['quantity'];
421 | }
422 |
423 | $charge = $stripe->request_charge( $amount, $source, $description, $receipt_email, $metadata );
424 |
425 | if ( is_wp_error( $charge ) ) {
426 | // A failure happened, since we don't expose the exact details to the user we'll catch every failure here.
427 | // Remove the POST param of the token so it's not used again.
428 | unset( $_POST['tix_stripe_token'] );
429 |
430 | $camptix->log( 'Stripe charge failed', $order['attendee_id'], $charge, 'stripe' );
431 |
432 | return $camptix->payment_result(
433 | $payment_token,
434 | CampTix_Plugin::PAYMENT_STATUS_FAILED,
435 | array(
436 | 'errors' => $charge->errors,
437 | 'error_data' => $charge->error_data,
438 | )
439 | );
440 | }
441 |
442 | // This data shouldn't be stored in a log.
443 | unset( $charge['source'] );
444 |
445 | $payment_data = array(
446 | 'transaction_id' => $charge['id'],
447 | 'transaction_details' => array(
448 | 'raw' => array(
449 | 'token' => $source,
450 | 'charge' => $charge,
451 | ),
452 | ),
453 | );
454 |
455 | return $camptix->payment_result( $payment_token, CampTix_Plugin::PAYMENT_STATUS_COMPLETED, $payment_data );
456 | }
457 |
458 | /**
459 | * Adds a failure reason / code to the post-payment screen when the payment fails.
460 | *
461 | * @param string $payment_token
462 | * @param int $result
463 | * @param array|WP_Error $data
464 | */
465 | public function camptix_payment_result( $payment_token, $result, $data ) {
466 | /** @var CampTix_Plugin $camptix */
467 | global $camptix;
468 |
469 | if ( CampTix_Plugin::PAYMENT_STATUS_FAILED === $result && ! empty( $data ) ) {
470 | if ( isset( $data['errors'] ) ) {
471 | $codes = array_keys( $data['errors'] );
472 |
473 | $camptix->error(
474 | sprintf(
475 | __( 'Your payment has failed: %1$s (%2$s)', 'camptix' ),
476 | esc_html( $data['errors'][ $codes[0] ][0] ),
477 | esc_html( $codes[0] )
478 | )
479 | );
480 | } elseif ( isset( $data['transaction_details']['raw']['error'] ) ) {
481 | $error_data = $data['transaction_details']['raw']['error'];
482 |
483 | $message = $error_data['message'];
484 | $code = $error_data['code'];
485 | if ( isset( $error_data['decline_code'] ) ) {
486 | $code .= ' ' . $error_data['decline_code'];
487 | }
488 |
489 | $camptix->error(
490 | sprintf(
491 | __( 'Your payment has failed: %1$s (%2$s)', 'camptix' ),
492 | esc_html( $message ),
493 | esc_html( $code )
494 | )
495 | );
496 | } else {
497 | $camptix->error(
498 | __( 'Your payment has failed.', 'camptix' )
499 | );
500 | }
501 |
502 | // Unfortunately there's no way to remove the following failure message, but at least ours will display first:
503 | // A payment error has occurred, looks like chosen payment method is not responding. Please try again later.
504 | }
505 | }
506 |
507 | /**
508 | * Submits a single, user-initiated refund request to Stripe and returns the result.
509 | *
510 | * @param string $payment_token
511 | *
512 | * @return int One of the CampTix_Plugin::PAYMENT_STATUS_{status} constants
513 | */
514 | public function payment_refund( $payment_token ) {
515 | /** @var CampTix_Plugin $camptix */
516 | global $camptix;
517 |
518 | $result = $this->send_refund_request( $payment_token );
519 |
520 | if ( CampTix_Plugin::PAYMENT_STATUS_REFUND_FAILED === $result['status'] ) {
521 | $camptix->log( 'Stripe refund failed', $order['attendee_id'], $refund, 'stripe' );
522 |
523 | return $camptix->payment_result(
524 | $payment_token,
525 | CampTix_Plugin::PAYMENT_STATUS_REFUND_FAILED,
526 | $result
527 | );
528 | }
529 |
530 | return $camptix->payment_result( $payment_token, CampTix_Plugin::PAYMENT_STATUS_REFUNDED, $result );
531 | }
532 |
533 | /**
534 | * Send a request to Stripe to refund a transaction.
535 | *
536 | * @param string $payment_token
537 | *
538 | * @return array
539 | */
540 | public function send_refund_request( $payment_token ) {
541 | /** @var CampTix_Plugin $camptix */
542 | global $camptix;
543 |
544 | $result = array(
545 | 'status' => CampTix_Plugin::PAYMENT_STATUS_REFUND_FAILED,
546 | 'transaction_id' => '',
547 | 'refund_transaction_id' => '',
548 | 'refund_transaction_details' => '',
549 | );
550 |
551 | $order = $this->get_order( $payment_token );
552 | $transaction_id = $camptix->get_post_meta_from_payment_token( $payment_token, 'tix_transaction_id' );
553 |
554 | if ( empty( $order ) || ! $transaction_id ) {
555 | $camptix->log( 'Could not refund because could not find order', null, array( 'payment_token' => $payment_token ), 'stripe' );
556 |
557 | return $result;
558 | }
559 |
560 | $metadata = array(
561 | 'Refund reason' => filter_input( INPUT_POST, 'tix_refund_request_reason', FILTER_SANITIZE_STRING ),
562 | );
563 |
564 | // Create a new Idempotency token for the refund request.
565 | // The same token can't be used for both a charge and a refund.
566 | $idempotency_token = md5( 'tix-idempotency-token' . $payment_token . time() . rand( 1, 9999 ) );
567 | $credentials = $this->get_api_credentials();
568 |
569 | $stripe = new CampTix_Stripe_API_Client( $idempotency_token, $credentials['api_secret_key'] );
570 | $refund = $stripe->request_refund( $transaction_id, $metadata );
571 |
572 | if ( is_wp_error( $refund ) ) {
573 | $result['refund_transaction_details'] = array(
574 | 'errors' => $refund->errors,
575 | 'error_data' => $refund->error_data,
576 | );
577 |
578 | return $result;
579 | }
580 |
581 | $result['status'] = CampTix_Plugin::PAYMENT_STATUS_REFUNDED;
582 | $result['transaction_id'] = $refund['charge'];
583 | $result['refund_transaction_id'] = $refund['id'];
584 | $result['refund_transaction_details'] = array(
585 | 'raw' => array(
586 | 'refund_transaction_id' => $refund['id'],
587 | 'refund' => $refund,
588 | ),
589 | );
590 |
591 | return $result;
592 | }
593 | }
594 |
595 | camptix_register_addon( 'CampTix_Payment_Method_Stripe' );
596 |
597 | /**
598 | * Class CampTix_Stripe_API_Client
599 | *
600 | * A simple client for the Stripe API to handle the simple needs of CampTix.
601 | */
602 | class CampTix_Stripe_API_Client {
603 | /**
604 | * @var string
605 | */
606 | protected $payment_token = '';
607 |
608 | /**
609 | * @var string
610 | */
611 | protected $api_secret_key = '';
612 |
613 | /**
614 | * @var string
615 | */
616 | protected $user_agent = '';
617 |
618 | /**
619 | * @var string
620 | */
621 | protected $currency = '';
622 |
623 | /**
624 | * CampTix_Stripe_API_Client constructor.
625 | *
626 | * @param string $payment_token
627 | * @param string $api_secret_key
628 | */
629 | public function __construct( $payment_token, $api_secret_key ) {
630 | /* @var CampTix_Plugin $camptix */
631 | global $camptix;
632 |
633 | $camptix_options = $camptix->get_options();
634 |
635 | $this->payment_token = $payment_token;
636 | $this->api_secret_key = $api_secret_key;
637 | $this->user_agent = 'CampTix/' . $camptix->version;
638 | $this->currency = $camptix_options['currency'];
639 | }
640 |
641 | /**
642 | * Get the API's endpoint URL for the given request type.
643 | *
644 | * @param string $request_type 'charge' or 'refund'.
645 | *
646 | * @return string
647 | */
648 | protected function get_request_url( $request_type ) {
649 | $request_url = '';
650 |
651 | $api_base = 'https://api.stripe.com/';
652 |
653 | switch ( $request_type ) {
654 | case 'charge' :
655 | $request_url = $api_base . 'v1/charges';
656 | break;
657 | case 'refund' :
658 | $request_url = $api_base . 'v1/refunds';
659 | break;
660 | }
661 |
662 | return $request_url;
663 | }
664 |
665 | /**
666 | * Send a request to the API and do basic processing on the response.
667 | *
668 | * @param string $type The type of API request. 'charge' or 'refund'.
669 | * @param array $args Parameters that will populate the body of the request.
670 | *
671 | * @return array|WP_Error
672 | */
673 | protected function send_request( $type, $args ) {
674 | $request_url = $this->get_request_url( $type );
675 |
676 | if ( ! $request_url ) {
677 | return new WP_Error(
678 | 'camptix_stripe_invalid_request_type',
679 | sprintf(
680 | __( '%s is not a valid request type.', 'camptix' ),
681 | esc_html( $type )
682 | )
683 | );
684 | }
685 |
686 | $request_args = array(
687 | 'user-agent' => $this->user_agent,
688 | 'timeout' => 30, // The default of 5 seconds can result in frequent timeouts.
689 |
690 | 'body' => $args,
691 |
692 | 'headers' => array(
693 | 'Authorization' => 'Bearer ' . $this->api_secret_key,
694 | 'Idempotency-Key' => $this->payment_token,
695 | ),
696 | );
697 |
698 | $response = wp_remote_post( $request_url, $request_args );
699 |
700 | if ( is_wp_error( $response ) ) {
701 | return $response;
702 | }
703 |
704 | $response_code = wp_remote_retrieve_response_code( $response );
705 | $response_body = json_decode( wp_remote_retrieve_body( $response ), true );
706 |
707 | if ( 200 !== $response_code ) {
708 | if ( ! is_array( $response_body ) || ! isset( $response_body['error'] ) ) {
709 | return new WP_Error(
710 | 'camptix_stripe_unexpected_response',
711 | __( 'An unexpected error occurred.', 'camptix' ),
712 | $response
713 | );
714 | }
715 |
716 | return $this->handle_error( $response_code, $response_body['error'] );
717 | }
718 |
719 | return $response_body;
720 | }
721 |
722 | /**
723 | * Parse error codes and messages from the API.
724 | *
725 | * @param int $error_code
726 | * @param array $error_content
727 | *
728 | * @return WP_Error
729 | */
730 | protected function handle_error( $error_code, $error_content ) {
731 | $error = new WP_Error();
732 |
733 | switch ( $error_content['type'] ) {
734 | case 'card_error' :
735 | if ( isset( $error_content['message'] ) ) {
736 | $reason = $error_content['message'];
737 | } elseif ( isset( $error_content['decline_code'] ) ) {
738 | $reason = $error_content['decline_code'];
739 | } elseif ( isset( $error_content['code'] ) ) {
740 | $reason = $error_content['code'];
741 | } else {
742 | $reason = __( 'Unspecified error', 'camptix' );
743 | }
744 |
745 | $message = sprintf(
746 | __( 'Card error: %s', 'camptix' ),
747 | esc_html( $reason )
748 | );
749 | break;
750 | default :
751 | $message = sprintf( __( '%d error: %s', 'camptix' ), $error_code, esc_html( $error_content['type'] ) );
752 | break;
753 | }
754 |
755 | $error->add(
756 | sprintf( 'camptix_stripe_request_error_%d', $error_code ),
757 | $message,
758 | $error_content
759 | );
760 |
761 | return $error;
762 | }
763 |
764 | /**
765 | * Send a charge request to the API.
766 | *
767 | * @param int $amount The amount to charge. Should already be converted to its fractional unit.
768 | * @param string $source The Stripe token.
769 | * @param string $description The description of the transaction that the charge is for.
770 | * @param string $receipt_email The email address to send the receipt to.
771 | * @param array $metadata Associative array of extra data to store with the transaction.
772 | *
773 | * @return array|WP_Error
774 | */
775 | public function request_charge( $amount, $source, $description, $receipt_email, $metadata = array() ) {
776 | $statement_descriptor = sanitize_text_field( $description );
777 | $statement_descriptor = str_replace( array( '<', '>', '"', "'" ), '', $statement_descriptor );
778 | $statement_descriptor = $this->trim_string( $statement_descriptor, 22 );
779 |
780 | $args = array(
781 | 'amount' => $amount,
782 | 'currency' => $this->currency,
783 | 'description' => $description,
784 | 'statement_descriptor' => $statement_descriptor,
785 | 'source' => $source,
786 | 'receipt_email' => $receipt_email,
787 | );
788 |
789 | if ( is_array( $metadata ) && ! empty( $metadata ) ) {
790 | $args['metadata'] = $this->clean_metadata( $metadata );
791 | }
792 |
793 | return $this->send_request( 'charge', $args );
794 | }
795 |
796 | /**
797 | * Send a refund request to the API.
798 | *
799 | * @param string $transaction_id
800 | * @param array $metadata Associative array of extra data to store with the transaction.
801 | *
802 | * @return array|WP_Error
803 | */
804 | public function request_refund( $transaction_id, $metadata = array() ) {
805 | $args = array(
806 | 'charge' => $transaction_id,
807 | 'reason' => 'requested_by_customer',
808 | );
809 |
810 | if ( is_array( $metadata ) && ! empty( $metadata ) ) {
811 | $args['metadata'] = $this->clean_metadata( $metadata );
812 | }
813 |
814 | return $this->send_request( 'refund', $args );
815 | }
816 |
817 | /**
818 | * Trim a string to a certain number of characters.
819 | *
820 | * @param string $string The original string.
821 | * @param int $chars The max number of characters for the string.
822 | * @param string $suffix A suffix to append if the string exceeds the max.
823 | *
824 | * @return string
825 | */
826 | protected function trim_string( $string, $chars = 500, $suffix = '...' ) {
827 | if ( strlen( $string ) > $chars ) {
828 | if ( function_exists( 'mb_substr' ) ) {
829 | $string = mb_substr( $string, 0, ( $chars - mb_strlen( $suffix ) ) ) . $suffix;
830 | } else {
831 | $string = substr( $string, 0, ( $chars - strlen( $suffix ) ) ) . $suffix;
832 | }
833 | }
834 |
835 | return $string;
836 | }
837 |
838 | /**
839 | * Clean up an array of metadata before passing to Stripe.
840 | *
841 | * @see https://stripe.com/docs/api#metadata
842 | *
843 | * @param array $metadata An associative array of metadata.
844 | *
845 | * @return array
846 | */
847 | protected function clean_metadata( $metadata = array() ) {
848 | $cleaned = array();
849 |
850 | foreach ( $metadata as $key => $val ) {
851 | // A Stripe transaction can only have 20 metadata keys.
852 | if ( count( $cleaned ) > 20 ) {
853 | return $cleaned;
854 | }
855 |
856 | // Trim the key to 40 chars.
857 | $key = $this->trim_string( $key, 40, '' );
858 |
859 | // Trim the val to 500 chars.
860 | $val = $this->trim_string( $val );
861 |
862 | $cleaned[ $key ] = $val;
863 | }
864 |
865 | return $cleaned;
866 | }
867 | }
868 |
--------------------------------------------------------------------------------