My Calendar - Version 2.5.0

Version Description

  • Update hcalendar structures
  • Better handling when updating event taxonomies
  • Options to restrict management of events by category / user
  • UI Clean up
  • Don't display format toggle on mobile if automatic format switching enabled
  • Add custom date option to upcoming events shortcode builder
  • Improved error message if user creates event with an invalid recurring cycle
  • Updated template editor; ability to create custom templates.
  • Add option to add new dates for an existing event.
  • For single event, show closest available date if no/invalid date ID provided.
  • Added first occurrence data to core event object
  • New template tag: {related} to list other events in the same group
  • New loading indicator for AJAX navigation
  • New filter to modify event classes
  • New function to generate event classes
  • Reduce number of strings in plug-in to reduce burden on translators
  • Multisite: ability to display calendar for any site on any other site
  • in my_calendar_draw_event(), add filter to hide additional days of events
  • Improved HTML filtering to allow input elements and schema.org attributes.
  • Add support for Google Maps API key field, now required for use of Google Maps on new sites
  • Add 'today' keyword for the upcoming events 'to' attribute
  • Updates to Help documentation
  • Bug fix: auto assign events with no category to 'General'
  • Bug fix: some user select lists overwrote select list options
  • Bug fix: new events with no times entered need to be created as all day events
  • Bug fix: wrong number of arguments passed to mass delete events hook
  • Bug fix: Custom JS incorrectly escaped in Script manager
  • Bug fix: removed numerous notices
  • Bug fix: improved handling of missing event posts
  • Bug fix: allow more HTML elements & attributes
  • Bug fix: misc. notices

Breaking Changes: * Breaking change: minor changes to classes to improve structured data in microformats * Breaking change: upcoming events widget no longer uses ID 'upcoming-events'; use class '.upcoming-events' * Breaking change: today's events widget no longer uses ID 'todays-events'; use class '.todays-events'

Download this release

Release Info

Developer joedolson
Plugin Icon 128x128 My Calendar
Version 2.5.0
Comparing to
See all releases

Code changes from version 2.4.21 to 2.5.0

css/mc-styles.css CHANGED
@@ -251,14 +251,15 @@ span.mc-notice {
251
float: right
252
}
253
254
- input[id=e_label], input[id=e_title], input[id=location_label], input[id=mc_twitter] {
255
font-size: 1.4em;
256
- padding: 5px;
257
- width: 100%
258
}
259
260
label[for=mc_twitter] {
261
- background: url(../images/twitter.png) no-repeat;
262
padding-left: 20px;
263
}
264
@@ -353,10 +354,9 @@ strong.label {
353
}
354
355
.jd-my-calendar .ui-accordion-header, .mc-settings-page #mc-sortable li {
356
- border: 1px solid #ccc;
357
- border-radius: 2px;
358
- background: rgba(0, 0, 0, .05);
359
- padding: 1px 1em;
360
margin: 1px 0;
361
}
362
@@ -378,8 +378,27 @@ strong.label {
378
cursor: move;
379
}
380
381
.jd-my-calendar .ui-accordion-header-active {
382
- background: #fff;
383
}
384
385
#mc-sortable .mc-calendar {
@@ -420,11 +439,17 @@ strong.label {
420
}
421
422
.mc_permissions {
423
- padding: .5em;
424
}
425
426
- .mc_permissions:nth-of-type(odd) {
427
- background: rgba( 0,0,0, .05 );
428
}
429
430
.jd-my-calendar .checkboxes li {
@@ -434,10 +459,6 @@ strong.label {
434
margin: 2px;
435
}
436
437
- .jd-my-calendar .mc_permissions:nth-of-type(odd) .checkboxes li {
438
- background: transparent;
439
- }
440
-
441
.jd-my-calendar .checkboxes li:hover {
442
background: #fff;
443
}
@@ -545,6 +566,49 @@ strong.label {
545
float: right;
546
}
547
548
.postbox.sell {
549
background: #fff;
550
color: #000;
@@ -604,11 +668,17 @@ tr.problem .error {
604
}
605
606
.mc-support-me p, .mc-support-me a {
607
- color: #fff;
608
}
609
610
.mc-support-me a:hover, .mc-support-me a:focus {
611
- text-decoration: none;
612
}
613
614
.jd-my-calendar .mc-none {
@@ -625,6 +695,10 @@ tr.problem .error {
625
.jd-my-calendar .tablenav {
626
float: none;
627
}
628
629
.jcd-narrow {
630
width: 100%;
251
float: right
252
}
253
254
+ input[id=e_label], input[id=e_title], input[id=location_label], textarea[id=mc_twitter] {
255
font-size: 1.4em;
256
+ padding: 9px;
257
+ width: 100%;
258
+ margin-bottom: .5em;
259
}
260
261
label[for=mc_twitter] {
262
+ background: url(../images/twitter.png) left 50% no-repeat;
263
padding-left: 20px;
264
}
265
354
}
355
356
.jd-my-calendar .ui-accordion-header, .mc-settings-page #mc-sortable li {
357
+ border: 1px solid #ddd;
358
+ background: rgba(0, 0, 0, .10);
359
+ padding: .5em 1em;
360
margin: 1px 0;
361
}
362
378
cursor: move;
379
}
380
381
+ input[name="mc_uri"] {
382
+ width: 100%;
383
+ padding: 5px;
384
+ font-size: 1.4em;
385
+ }
386
+
387
+ #mc-generator .custom {
388
+ background: #f6f6f6;
389
+ padding: 1em;
390
+ }
391
+
392
.jd-my-calendar .ui-accordion-header-active {
393
+ background: rgba(0, 0, 0, .05);
394
+ }
395
+
396
+ .jd-my-calendar .ui-accordion-header .dashicons:before {
397
+ content: "\f132";
398
+ }
399
+
400
+ .jd-my-calendar .ui-accordion-header-active .dashicons:before {
401
+ content: "\f460";
402
}
403
404
#mc-sortable .mc-calendar {
439
}
440
441
.mc_permissions {
442
+ float: left;
443
+ width: 33%;
444
+ margin-bottom: 1em;
445
}
446
447
+ .jd-my-calendar .mc_permissions .checkboxes li {
448
+ display: block;
449
+ }
450
+
451
+ .mc_permissions fieldset {
452
+ padding: 1em;
453
}
454
455
.jd-my-calendar .checkboxes li {
459
margin: 2px;
460
}
461
462
.jd-my-calendar .checkboxes li:hover {
463
background: #fff;
464
}
566
float: right;
567
}
568
569
+ .mc_add_new {
570
+ position: relative;
571
+ z-index: 200;
572
+ padding: 1em;
573
+ background: #fff;
574
+ box-shadow: 0 0 3px #777;
575
+ }
576
+
577
+ .mc_add_new .clonedInput {
578
+ border-bottom: none;
579
+ margin-bottom: 0;
580
+ }
581
+
582
+ .mc-twitter {
583
+ padding: .5em 1em;
584
+ background: #213e7f;
585
+ color: #fff;
586
+ font-size: 1.1em;
587
+ }
588
+
589
+ .add-occurrence .dashicons:before {
590
+ content: "\f132";
591
+ }
592
+
593
+ .jd-my-calendar .postbox .hndle {
594
+ cursor: auto;
595
+ }
596
+
597
+ .save-occurrence {
598
+ position: absolute;
599
+ right: 1em;
600
+ bottom: 1em;
601
+ }
602
+
603
+ .add-occurrence[aria-expanded^="true"] .dashicons:before {
604
+ content: "\f460";
605
+ }
606
+
607
+ .template-description {
608
+ font-weight: 700;
609
+ font-size: 1.1em;
610
+ }
611
+
612
.postbox.sell {
613
background: #fff;
614
color: #000;
668
}
669
670
.mc-support-me p, .mc-support-me a {
671
+ color: #f3f3f3;
672
+ }
673
+
674
+ .wrap ul.list {
675
+ list-style-type: disc;
676
+ margin-left: 1.5em;
677
}
678
679
.mc-support-me a:hover, .mc-support-me a:focus {
680
+ text-decoration: none !important;
681
+ color: #fff !important;
682
}
683
684
.jd-my-calendar .mc-none {
695
.jd-my-calendar .tablenav {
696
float: none;
697
}
698
+
699
+ .mc_permissions {
700
+ width: 50%;
701
+ }
702
703
.jcd-narrow {
704
width: 100%;
css/reset.css CHANGED
@@ -85,6 +85,48 @@ button.mc-text-button:hover, button .mc-text-button:focus {
85
text-decoration: underline;
86
background-color: transparent;
87
}
88
89
/**
90
* This is focus passed to a div for the purpose of navigation; visible focus not required.
85
text-decoration: underline;
86
background-color: transparent;
87
}
88
+ .mc-main .mc-loading {
89
+ position: absolute;
90
+ top: 10%;
91
+ left: 50%;
92
+ margin: -20px 0 0 -20px;
93
+ height: 40px;
94
+ width: 40px;
95
+ border: 2px solid rgba(0,0,0,0.3);
96
+ border-left-color: rgba(0,0,0,0.7);
97
+ background-color: rgba(0,0,0,0.05);
98
+ -webkit-border-radius: 50px;
99
+ -moz-border-radius: 50px;
100
+ border-radius: 50px;
101
+ -webkit-animation: animation-rotate 1250ms linear infinite;
102
+ -moz-animation: animation-rotate 1250ms linear infinite;
103
+ -o-animation: animation-rotate 1250ms linear infinite;
104
+ animation: animation-rotate 1250ms linear infinite;
105
+ }
106
+
107
+ @-webkit-keyframes animation-rotate {
108
+ 100% {
109
+ -webkit-transform: rotate(360deg);
110
+ }
111
+ }
112
+
113
+ @-moz-keyframes animation-rotate {
114
+ 100% {
115
+ -moz-transform: rotate(360deg);
116
+ }
117
+ }
118
+
119
+ @-o-keyframes animation-rotate {
120
+ 100% {
121
+ -o-transform: rotate(360deg);
122
+ }
123
+ }
124
+
125
+ @keyframes animation-rotate {
126
+ 100% {
127
+ transform: rotate(360deg);
128
+ }
129
+ }
130
131
/**
132
* This is focus passed to a div for the purpose of navigation; visible focus not required.
includes/general-utilities.php ADDED
@@ -0,0 +1,65 @@
1
+ <?php
2
+ if ( ! defined( 'ABSPATH' ) ) {
3
+ exit;
4
+ } // Exit if accessed directly
5
+
6
+ function mc_switch_sites() {
7
+ if ( function_exists( 'is_multisite' ) && is_multisite() ) {
8
+ if ( get_site_option( 'mc_multisite' ) == 2 && my_calendar_table() != my_calendar_table( 'global' ) ) {
9
+ if ( get_option( 'mc_current_table' ) == '1' ) {
10
+ // can post to either, but is currently set to post to central table
11
+ return true;
12
+ }
13
+ } else if ( get_site_option( 'mc_multisite' ) == 1 && my_calendar_table() != my_calendar_table( 'global' ) ) {
14
+ // can only post to central table
15
+ return true;
16
+ }
17
+ }
18
+
19
+ return false;
20
+ }
21
+
22
+
23
+ function mc_tweet_approval( $prev, $new ) {
24
+ if ( function_exists( 'jd_doTwitterAPIPost' ) && isset( $_POST['mc_twitter'] ) && trim( $_POST['mc_twitter'] ) != '' ) {
25
+ if ( ( $prev == 0 || $prev == 2 ) && $new == 1 ) {
26
+ jd_doTwitterAPIPost( stripslashes( $_POST['mc_twitter'] ) );
27
+ }
28
+ }
29
+ }
30
+
31
+
32
+ function mc_flatten_event_array( $events ) {
33
+ $flat = array();
34
+ foreach( $events as $event ) {
35
+ foreach( $event as $e ) {
36
+ $flat[] = $e;
37
+ }
38
+ }
39
+
40
+ return $flat;
41
+ }
42
+
43
+
44
+ add_action( 'admin_menu', 'mc_add_outer_box' );
45
+
46
+ // begin add boxes
47
+ function mc_add_outer_box() {
48
+ add_meta_box( 'mcs_add_event', __('My Calendar Event', 'my-calendar'), 'mc_add_inner_box', 'mc-events', 'side','high' );
49
+ }
50
+
51
+ function mc_add_inner_box() {
52
+ global $post;
53
+ $event_id = get_post_meta( $post->ID, '_mc_event_id', true );
54
+ if ( $event_id ) {
55
+ $url = admin_url( 'admin.php?page=my-calendar&mode=edit&event_id='.$event_id );
56
+ $event = mc_get_event_core( $event_id );
57
+ $content = "<p><strong>" . mc_kses_post( $event->event_title ) . '</strong><br />' . $event->event_begin . ' @ ' . $event->event_time . "</p>";
58
+ if ( $event->event_label != '' ) {
59
+ $content .= "<p>" . sprintf( __( '<strong>Location:</strong> %s', 'my-calendar' ), mc_kses_post( $event->event_label ) ) . "</p>";
60
+ }
61
+ $content .= "<p>" . sprintf( __( '<a href="%s">Edit event</a>.', 'my-calendar' ), $url ) . "</p>";
62
+
63
+ echo $content;
64
+ }
65
+ }
includes/kses.php ADDED
@@ -0,0 +1,87 @@
1
+ <?php
2
+ if ( ! defined( 'ABSPATH' ) ) {
3
+ exit;
4
+ } // Exit if accessed directly
5
+
6
+
7
+ function mc_kses_post( $string ) {
8
+ if ( !is_string( $string ) ) {
9
+ return $string;
10
+ } else {
11
+ return wp_kses( $string, 'mycalendar' );
12
+ }
13
+ }
14
+
15
+
16
+ /**
17
+ * My Calendar needs to allow input and select in posts and a variety of other key elements; also provide support for schema.org data.
18
+ *
19
+ * Call using wp_kses( $data, 'mycalendar' );
20
+ */
21
+ add_filter( 'wp_kses_allowed_html', 'mc_allowed_tags', 10, 2 );
22
+ function mc_allowed_tags( $tags, $context ) {
23
+ if ( $context == 'mycalendar' ) {
24
+ global $allowedposttags;
25
+ $tags = $allowedposttags;
26
+ $tags['input'] = array(
27
+ 'type' => true,
28
+ 'value' => true,
29
+ 'name' => true,
30
+ 'class' => true,
31
+ 'aria-labelledby' => true,
32
+ 'aria-describedby' => true,
33
+ 'disabled' => true,
34
+ 'readonly' => true,
35
+ 'min' => true,
36
+ 'max' => true,
37
+ );
38
+ $tags['select'] = array(
39
+ 'name' => true,
40
+ 'id' => true,
41
+ 'class' => true
42
+ );
43
+ $tags['span'] = array(
44
+ 'dir' => true,
45
+ 'align' => true,
46
+ 'lang' => true,
47
+ 'xml:lang' => true,
48
+ 'itemprop' => true,
49
+ 'itemscope' => true,
50
+ 'itemtype' => true,
51
+ );
52
+ $tags['button'] = array(
53
+ 'name' => true,
54
+ 'type' => true,
55
+ 'disabled' => true,
56
+ );
57
+ $tags['form'] = array(
58
+ 'action' => true,
59
+ 'method' => true,
60
+ 'class' => true,
61
+ 'id' => true,
62
+ 'tabindex' => true,
63
+ );
64
+ $tags['div'] = array(
65
+ 'class' => true,
66
+ 'id' => true,
67
+ 'aria-live' => true,
68
+ );
69
+ $tags['fieldset'] = array();
70
+ $tags['legend'] = array();
71
+ $tags['p'] = array(
72
+ 'class' => true,
73
+ );
74
+ $tags['img'] = array(
75
+ 'class' => true,
76
+ 'src' => true,
77
+ 'alt' => true,
78
+ 'width' => true,
79
+ 'height' => true,
80
+ 'id' => true,
81
+ 'longdesc' => true,
82
+ 'tabindex' => true
83
+ );
84
+ }
85
+
86
+ return $tags;
87
+ }
js/ajax.js CHANGED
@@ -2,8 +2,7 @@
2
$(function () {
3
// Delete single instances of recurring events.
4
$( '.mc_response' ).hide();
5
- $('button.delete_occurrence').on( 'click', function (e) {
6
- e.preventDefault();
7
var value = $(this).attr( 'data-value' );
8
var data = {
9
'action': mc_data.action,
@@ -17,6 +16,45 @@
17
$('.mc_response').text( response.response ).show( 300 );
18
}, "json" );
19
});
20
// display notice informing users of lack of support for recur month by day
21
$( '.mc_recur_notice' ).hide();
22
$( '#e_recur' ).on( 'change', function (e) {
2
$(function () {
3
// Delete single instances of recurring events.
4
$( '.mc_response' ).hide();
5
+ $('button.delete_occurrence').on( 'click', function () {
6
var value = $(this).attr( 'data-value' );
7
var data = {
8
'action': mc_data.action,
16
$('.mc_response').text( response.response ).show( 300 );
17
}, "json" );
18
});
19
+
20
+ $( '.mc_add_new' ).hide();
21
+
22
+ $( 'button.add-occurrence').on( 'click', function() {
23
+ var expanded = $( this ).attr( 'aria-expanded' );
24
+ if ( expanded == 'true' ) {
25
+ $( this ).attr( 'aria-expanded', 'false' );
26
+ } else {
27
+ $( this ).attr( 'aria-expanded', 'true' );
28
+ }
29
+ $( '.mc_add_new' ).toggle();
30
+ });
31
+
32
+ $( 'button.save-occurrence').on( 'click', function() {
33
+ var date = $( '#r_begin' ).val();
34
+ var begin = $( '#r_time' ).val();
35
+ var end = $( '#r_endtime' ).val();
36
+ var enddate = $( '#r_enddate' ).val();
37
+ var event_id = $( 'input[name="event_id"]' ).val();
38
+ var group_id = $( 'input[name="event_group_id"]' ).val();
39
+
40
+ var data = {
41
+ 'action': mc_data.recur,
42
+ 'event_id': event_id,
43
+ 'group_id': group_id,
44
+ 'event_date' : date,
45
+ 'event_time' : begin,
46
+ 'event_endtime' : end,
47
+ 'event_enddate' : enddate,
48
+ 'security': mc_data.security
49
+ };
50
+ $.post( ajaxurl, data, function (response) {
51
+ if ( response.success == 1 ) {
52
+ $( '.instance-list' ).append( '<li class="new"><strong>+</strong> ' + date + ' ' + begin + '</li>' );
53
+ }
54
+ $('.mc_response').text( response.response ).show( 300 );
55
+ }, "json" );
56
+ });
57
+
58
// display notice informing users of lack of support for recur month by day
59
$( '.mc_recur_notice' ).hide();
60
$( '#e_recur' ).on( 'change', function (e) {
js/gmap3.js ADDED
@@ -0,0 +1,1131 @@
1
+ /*!
2
+ * GMAP3 Plugin for jQuery
3
+ * Version : 7.1
4
+ * Date : 2016/04/17
5
+ * Author : DEMONTE Jean-Baptiste
6
+ * Contact : jbdemonte@gmail.com
7
+ * Web site : http://gmap3.net
8
+ * Licence : GPL-3.0+
9
+ */
10
+ (function ($, window, document) {
11
+ "use strict";
12
+
13
+ var gm, services = {}, loadOptions,
14
+
15
+ // Proxify functions to get shorter minimized code
16
+ when = $.when,
17
+ extend = $.extend,
18
+ isArray = $.isArray,
19
+ isFunction = $.isFunction,
20
+ deferred = $.Deferred;
21
+
22
+ /**
23
+ * Duplicate option to never modify original object
24
+ * @param {Object} options
25
+ * @returns {Object}
26
+ */
27
+ function dupOpts(options) {
28
+ return extend(true, {}, options || {});
29
+ }
30
+
31
+ /**
32
+ * Slice an array like
33
+ * @params {Array|Object}
34
+ * @params {Number} [start]
35
+ * @params {Number} [end]
36
+ * @returns {Array}
37
+ */
38
+ function slice() {
39
+ var fn = Array.prototype.slice,
40
+ args = fn.call(arguments, 1);
41
+ return fn.apply(arguments[0], args);
42
+ }
43
+
44
+ /**
45
+ * Return true if value is undefined
46
+ * @param {*} value
47
+ * @returns {Boolean}
48
+ */
49
+ function isUndefined(value) {
50
+ return typeof value === 'undefined';
51
+ }
52
+
53
+ /**
54
+ * Equivalent to Promise.all
55
+ * @param {Deferred[]} deferreds
56
+ * @returns {Deferred}
57
+ */
58
+ function all(deferreds) {
59
+ return when.apply($, deferreds);
60
+ }
61
+
62
+ /**
63
+ * Equivalent to Promise.resolve
64
+ * @param {*} value
65
+ * @returns {Deferred}
66
+ */
67
+ function resolved(value) {
68
+ return when().then(function () {
69
+ return value;
70
+ });
71
+ }
72
+
73
+ /**
74
+ * return the distance between 2 latLng in meters
75
+ * @param {LatLng} origin
76
+ * @param {LatLng} destination
77
+ * @returns {Number}
78
+ **/
79
+ function distanceInMeter(origin, destination) {
80
+ var m = Math,
81
+ pi = m.PI,
82
+ e = pi * origin.lat() / 180,
83
+ f = pi * origin.lng() / 180,
84
+ g = pi * destination.lat() / 180,
85
+ h = pi * destination.lng() / 180,
86
+ cos = m.cos,
87
+ sin = m.sin;
88
+ return 1000 * 6371 * m.acos(m.min(cos(e) * cos(g) * cos(f) * cos(h) + cos(e) * sin(f) * cos(g) * sin(h) + sin(e) * sin(g), 1));
89
+ }
90
+
91
+ function ready(fn) {
92
+ if (document.readyState != 'loading'){
93
+ fn();
94
+ } else {
95
+ document.addEventListener('DOMContentLoaded', fn);
96
+ }
97
+ }
98
+
99
+ function serialize(obj) {
100
+ return objectKeys(obj).map(function (key) {
101
+ return encodeURIComponent(key) + "=" + encodeURIComponent(obj[key]);
102
+ }).join("&");
103
+ }
104
+
105
+ // Auto-load google maps library if needed
106
+ (function () {
107
+ var dfd = deferred(),
108
+ cbName = '__gmap3',
109
+ script;
110
+
111
+ $.holdReady(true);
112
+
113
+ ready(function () {
114
+ if (window.google && window.google.maps || loadOptions === false) {
115
+ dfd.resolve();
116
+ } else {
117
+ // callback function - resolving promise after maps successfully loaded
118
+ window[cbName] = function () {
119
+ delete window[cbName];
120
+ dfd.resolve();
121
+ };
122
+ script = document.createElement('script');
123
+ script.type = 'text/javascript';
124
+ script.src = 'https://maps.googleapis.com/maps/api/js?callback=' + cbName + (loadOptions ? '&' + (typeof loadOptions === 'string' ? loadOptions : serialize(loadOptions)) : '');
125
+ $("head").append(script);
126
+ }
127
+ });
128
+
129
+ return dfd.promise();
130
+ })().then(function () {
131
+ $.holdReady(false);
132
+ });
133
+
134
+ /**
135
+ * Instantiate only once a google service
136
+ * @param {String} name
137
+ * @returns {Object}
138
+ */
139
+ function service(name) {
140
+ if (!services[name]) {
141
+ services[name] = gmElement(name);
142
+ }
143
+ return services[name];
144
+ }
145
+
146
+ /**
147
+ * Return GoogleMap Class (or overwritten by user) instance
148
+ * @param {String} name
149
+ * @returns {Object}
150
+ */
151
+ function gmElement(name) {
152
+ var cls = gm[name];
153
+
154
+ function F(args) {
155
+ return cls.apply(this, args);
156
+ }
157
+ F.prototype = cls.prototype;
158
+
159
+ return new F(slice(arguments, 1));
160
+ }
161
+
162
+ /**
163
+ * Resolve a GeocodeRequest
164
+ * https://developers.google.com/maps/documentation/javascript/geocoding
165
+ * @param {String|Object} request
166
+ * @returns {Deferred}
167
+ */
168
+ function geocode(request) {
169
+ var dfd = deferred();
170
+ if (typeof request === 'string') {
171
+ request = {
172
+ address: request
173
+ };
174
+ }
175
+ service('Geocoder').geocode(request, function(results, status) {
176
+ if (status === gm.GeocoderStatus.OK) {
177
+ dfd.resolve(results[0].geometry.location);
178
+ } else {
179
+ dfd.reject();
180
+ }
181
+ });
182
+ return dfd;
183
+ }
184
+
185
+ /**
186
+ * Callable function taking a parameter as string
187
+ * @callback StringCallback
188
+ * @param {String}
189
+ */
190
+
191
+ /**
192
+ * Split a string and execute a function on each item
193
+ * @param {String} str - Space separated list of items
194
+ * @param {StringCallback} fn - Callback function
195
+ */
196
+ function foreachStr(str, fn) {
197
+ str.split(' ').forEach(fn);
198
+ }
199
+
200
+ /**
201
+ * Execute a function on each items if items is an array and on items as a single element if it is not an array
202
+ * @param {Array|*} items - Items to execute foreach callback on
203
+ * @param {Function} fn - Callback function
204
+ */
205
+ function foreach(items, fn) {
206
+ (isArray(items) ? items : [items]).forEach(fn);
207
+ }
208
+
209
+ /**
210
+ * Return Object keys
211
+ * @param {Object} obj
212
+ * @returns {String[]}
213
+ */
214
+ function objectKeys(obj) {
215
+ return Object.keys(obj);
216
+ }
217
+
218
+ /**
219
+ * Return Object values
220
+ * @param {Object} obj
221
+ * @returns {*[]}
222
+ */
223
+ function objectValues(obj) {
224
+ return objectKeys(obj).map(function (key) {
225
+ return obj[key];
226
+ });
227
+ }
228
+
229
+ /**
230
+ * Resolution function
231
+ * @callback OptionCallback
232
+ * @param {Object} options
233
+ * @returns {Deferred|*}
234
+ */
235
+
236
+ /**
237
+ * Convert bounds from array [ n, e, s, w ] to google.maps.LatLngBounds
238
+ * @param {Object} options - Container of options.bounds
239
+ * @param {OptionCallback} fn
240
+ * @returns {Deferred}
241
+ */
242
+ function resolveLatLngBounds(options, fn) {
243
+ options = dupOpts(options);
244
+ if (options.bounds) {
245
+ options.bounds = toLatLngBound(options.bounds);
246
+ }
247
+ return resolved(fn(options));
248
+ }
249
+
250
+ /**
251
+ * Resolve an address location / convert a LatLng array to google.maps.LatLng object
252
+ * @param {Object} options
253
+ * @param {String} key - LatLng key name in options object
254
+ * @param {OptionCallback} fn
255
+ * @returns {Deferred}
256
+ */
257
+ function resolveLatLng(options, key, fn) {
258
+ var dfd = deferred();
259
+ options = dupOpts(options);
260
+ when()
261
+ .then(function () {
262
+ var address = options.address;
263
+ if (address) {
264
+ delete options.address;
265
+ return geocode(address).then(function (latLng) {
266
+ options[key] = latLng;
267
+ });
268
+ }
269
+ options[key] = toLatLng(options[key]);
270
+ })
271
+ .then(function () {
272
+ dfd.resolve(fn(options));
273
+ });
274
+ return dfd;
275
+ }
276
+
277
+ /**
278
+ * Convert an array of mixed LatLng to google.maps.LatLng object
279
+ * No address resolution here
280
+ * @param {Object} options
281
+ * @param {String} key - Array key name in options object
282
+ * @param {OptionCallback} fn
283
+ * @returns {Deferred}
284
+ */
285
+ function resolveArrayOfLatLng(options, key, fn) {
286
+ options = dupOpts(options);
287
+ options[key] = (options[key] || []).map(function (item) {
288
+ return toLatLng(item);
289
+ });
290
+ return resolved(fn(options));
291
+ }
292
+
293
+ /**
294
+ * Convert a LatLng array to google.maps.LatLng
295
+ * @param {Array|*} mixed
296
+ * @param {Boolean} [convertLiteral]
297
+ * @returns {LatLng}
298
+ */
299
+ function toLatLng(mixed, convertLiteral) {
300
+ return isArray(mixed) ? new gm.LatLng(mixed[0], mixed[1]) : (convertLiteral && mixed && !(mixed instanceof gm.LatLng) ? new gm.LatLng(mixed.lat, mixed.lng) : mixed);
301
+ }
302
+
303
+ /**
304
+ * Convert a LatLngBound array to google.maps.LatLngBound
305
+ * @param {Array|*} mixed
306
+ * @param {Boolean} [convertLiteral]
307
+ * @returns {LatLngBounds}
308
+ */
309
+ function toLatLngBound(mixed, convertLiteral) {
310
+ if (isArray(mixed)) {
311
+ return new gm.LatLngBounds({lat: mixed[2], lng: mixed[3]}, {lat: mixed[0], lng: mixed[1]});
312
+ } else if (convertLiteral && !mixed.getCenter){
313
+ return new gm.LatLngBounds({lat: mixed.south, lng: mixed.west}, {lat: mixed.north, lng: mixed.east});
314
+ }
315
+ return mixed;
316
+ }
317
+
318
+ /**
319
+ * Create a custom overlay view
320
+ * @param {Map} map
321
+ * @param {Object} options
322
+ * @returns {OverlayView}
323
+ */
324
+ function createOverlayView(map, options) {
325
+
326
+ var GMOverlayView = gm.OverlayView;
327
+
328
+ var $div = $(document.createElement("div"))
329
+ .css({
330
+ border: "none",
331
+ borderWidth: 0,
332
+ position: "absolute"
333
+ })
334
+ .append(options.content);
335
+
336
+ options = extend({x: 0, y: 0}, options);
337
+
338
+ if (options.position) {
339
+ options.position = toLatLng(options.position, true);
340
+ } else if (options.bounds) {
341
+ options.bounds = toLatLngBound(options.bounds, true);
342
+ }
343
+
344
+ /**
345
+ * Class OverlayView
346
+ * @constructor
347
+ */
348
+ function OverlayView() {
349
+ var self = this,
350
+ listeners = [];
351
+
352
+ GMOverlayView.call(self);
353
+ self.setMap(map);
354
+
355
+ function fromLatLngToDivPixel(latlng) {
356
+ return self.getProjection().fromLatLngToDivPixel(latlng);
357
+ }
358
+
359
+ self.onAdd = function () {
360
+ var panes = self.getPanes();
361
+ panes.overlayMouseTarget.appendChild($div[0]);
362
+ };
363
+
364
+ if (options.position) {
365
+ self.getPosition = function () {
366
+ return options.position;
367
+ };
368
+
369
+ self.setPosition = function (latlng) {
370
+ options.position = latlng;
371
+ self.draw();
372
+ };
373
+
374
+ self.draw = function () {
375
+ var ps = fromLatLngToDivPixel(options.position);
376
+ $div.css({
377
+ left: (ps.x + options.x) + 'px',
378
+ top: (ps.y + options.y) + 'px'
379
+ });
380
+ };
381
+ } else {
382
+ self.getBounds = function () {
383
+ return options.bounds;
384
+ };
385
+
386
+ self.setBounds = function (bounds) {
387
+ options.bounds = bounds;
388
+ self.draw();
389
+ };
390
+
391
+ self.draw = function() {
392
+ var sw = fromLatLngToDivPixel(options.bounds.getSouthWest());
393
+ var ne = fromLatLngToDivPixel(options.bounds.getNorthEast());
394
+
395
+ $div.css({
396
+ left: (sw.x + options.x) + 'px',
397
+ top: (ne.y + options.y) + 'px',
398
+ width: (ne.x - sw.x + options.x) + 'px',
399
+ height: (sw.y - ne.y + options.y) + 'px'
400
+ });
401
+ };
402
+ }
403
+
404
+ self.onRemove = function () {
405
+ listeners.map(function (handler) {
406
+ gm.event.removeListener(handler);
407
+ });
408
+ $div.remove();
409
+ self.$ = $div = null; // mem leaks
410
+ };
411
+
412
+ self.$ = $div;
413
+ }
414
+
415
+ OverlayView.prototype = new GMOverlayView();
416
+
417
+ return new OverlayView();
418
+ }
419
+
420
+ /**
421
+ * Return a map projection
422
+ * @param {Map} map
423
+ * @returns {*}
424
+ */
425
+ function getProjection(map) {
426
+ function Overlay() {
427
+ var self = this;
428
+ self.onAdd = self.onRemove = self.draw = function () {};
429
+ return gm.OverlayView.call(self);
430
+ }
431
+ Overlay.prototype = new gm.OverlayView();
432
+ var overlay = new Overlay();
433
+ overlay.setMap(map);
434
+ return overlay.getProjection();
435
+ }
436
+
437
+ /**
438
+ * Class used as event first parameter on clustering overlays
439
+ * @param {Cluster} cluster
440
+ * @param {Marker[]} markers
441
+ * @param {OverlayView} overlay
442
+ * @param {LatLngBounds} bounds
443
+ * @constructor
444
+ */
445
+ function ClusterOverlay(cluster, markers, overlay, bounds) {
446
+ var self = this;
447
+ self.cluster = cluster;
448
+ self.markers = markers;
449
+ self.$ = overlay.$;
450
+ self.overlay = overlay;
451
+
452
+ overlay.getBounds = function () {
453
+ return gmElement('LatLngBounds', bounds.getSouthWest(), bounds.getNorthEast());
454
+ };
455
+ }
456
+
457
+ /**
458
+ * Cluster Group definition.
459
+ * @typedef {Object} ClusterGroupDef
460
+ * @property {String|jQuery} content
461
+ * @property {Number} [x] Offset
462
+ * @property {Number} [y] Offset
463
+ */
464
+
465
+ /**
466
+ * Cluster evaluation function
467
+ * @callback clusterCallback
468
+ * @param {Marker[]} markers
469
+ * @return {ClusterGroupDef|undefined}
470
+ */
471
+
472
+ /**
473
+ * Class used to handle clustering
474
+ * @param {Map} map
475
+ * @param {Object} options
476
+ * @param {Integer} [options.size]
477
+ * @param {Object[]} [options.markers] markers definition
478
+ * @param {clusterCallback} [options.cb] callback used to evaluate clustering elements
479
+ * @constructor
480
+ */
481
+ function Cluster(map, options) {
482
+ var timer, igniter, previousViewHash, projection, filter,
483
+ self = this,
484
+ markers = [],
485
+ radius = (options.size || 200) >> 1,
486
+ enabled = true,
487
+ overlays = {},
488
+ handlers = [];
489
+
490
+ options = options || {};
491
+ options.markers = options.markers || [];
492
+
493
+ /**
494
+ * Cluster evaluation function
495
+ * @callback bindCallback
496
+ * @param {ClusterOverlay[]} instances
497
+ */
498
+
499
+ /**
500
+ * Bind a function to each current or future overlays
501
+ * @param {bindCallback} fn
502
+ */
503
+ self._b = function (fn) {
504
+ fn(objectValues(overlays));
505
+ handlers.push(fn);
506
+ };
507
+
508
+ /**
509
+ * Get the marker list
510
+ * @returns {Marker[]}
511
+ */
512
+ self.markers = function () {
513
+ return slice(markers);
514
+ };
515
+
516
+ /**
517
+ * Get the current groups
518
+ * @returns {ClusterOverlay[]}
519
+ */
520
+ self.groups = function () {
521
+ return objectValues(overlays);
522
+ };
523
+
524
+ /**
525
+ * Enable the clustering feature
526
+ */
527
+ self.enable = function () {
528
+ if (!enabled) {
529
+ enabled = true;
530
+ previousViewHash = '';
531
+ delayRedraw();
532
+ }
533
+ };
534
+
535
+
536
+ /**
537
+ * Disable the clustering feature
538
+ */
539
+ self.disable = function () {
540
+ if (enabled) {
541
+ enabled = false;
542
+ previousViewHash = '';
543
+ delayRedraw();
544
+ }
545
+ };
546
+
547
+ /**
548
+ * Add a marker
549
+ * @param {Marker} marker
550
+ */
551
+ self.add = function (marker) {
552
+ markers.push(marker);
553
+ previousViewHash = '';
554
+ delayRedraw();
555
+ };
556
+
557
+ /**
558
+ * Remove a marker
559
+ * @param {Marker} marker
560
+ */
561
+ self.remove = function (marker) {
562
+ markers = markers.filter(function (item) {
563
+ return item !== marker;
564
+ });
565
+ previousViewHash = '';
566
+ delayRedraw();
567
+ };
568
+
569
+ /**
570
+ * Filtering function, Cluster only handle those who return true
571
+ * @callback filterCallback
572
+ * @param {Marker} marker
573
+ * @returns {Boolean}
574
+ */
575
+
576
+ /**
577
+ * Set a filter function
578
+ * @param {filterCallback} fn
579
+ */
580
+ self.filter = function (fn) {
581
+ if (filter !== fn) {
582
+ filter = fn;
583
+ previousViewHash = '';
584
+ delayRedraw();
585
+ }
586
+ };
587
+
588
+ /**
589
+ * Generate extended visible bounds
590
+ * @returns {LatLngBounds}
591
+ */
592
+ function extendsMapBounds() {
593
+ var circle = gmElement('Circle', {
594
+ center: map.getCenter(),
595
+ radius: 1.15 * distanceInMeter(map.getCenter(), map.getBounds().getNorthEast()) // + 15%
596
+ });
597
+ return circle.getBounds();
598
+ }
599
+
600
+ /**
601
+ * Generate bounds extended by radius
602
+ * @param {LatLng} latLng
603
+ * @returns {LatLngBounds}
604
+ */
605
+ function extendsBounds(latLng) {
606
+ var p = projection.fromLatLngToDivPixel(latLng);
607
+ return gmElement('LatLngBounds',
608
+ projection.fromDivPixelToLatLng(gmElement('Point', p.x - radius, p.y + radius)),
609
+ projection.fromDivPixelToLatLng(gmElement('Point', p.x + radius, p.y - radius))
610
+ );
611
+ }
612
+
613
+ options.markers.map(function (opts) {
614
+ opts.position = toLatLng(opts.position);
615
+ markers.push(gmElement('Marker', opts));
616
+ });
617
+
618
+ /**
619
+ * Redraw clusters
620
+ */
621
+ function redraw() {
622
+ var keys, bounds, overlayOptions, hash, currentMarkers, viewHash,
623
+ zoom = map.getZoom(),
624
+ currentHashes = {},
625
+ newOverlays = [],
626
+ ignore = {};
627
+
628
+ viewHash = '' + zoom;
629
+
630
+ if (zoom > 3) {
631
+ bounds = extendsMapBounds();
632
+ foreach(markers, function (marker, index) {
633
+ if (!bounds.contains(marker.getPosition())) {
634
+ viewHash += '-' + index;
635
+ ignore[index] = true;
636
+ if (marker.getMap()) {
637
+ marker.setMap(null);
638
+ }
639
+ }
640
+ });
641
+ }
642
+ if (filter) {
643
+ foreach(markers, function (marker, index) {
644
+ if (!ignore[index] && !filter(marker)) {
645
+ viewHash += '-' + index;
646
+ ignore[index] = true;
647
+ if (marker.getMap()) {
648
+ marker.setMap(null);
649
+ }
650
+ }
651
+ });
652
+ }
653
+
654
+ if (viewHash === previousViewHash) {
655
+ return;
656
+ }
657
+ previousViewHash = viewHash;
658
+
659
+ foreach(markers, function (marker, index) {
660
+ if (ignore[index]) {
661
+ return;
662
+ }
663
+
664
+ keys = [index];
665
+ bounds = extendsBounds(marker.getPosition());
666
+
667
+ if (enabled) {
668
+ foreach(slice(markers, index + 1), function (marker, idx) {
669
+ idx += index + 1;
670
+ if (!ignore[idx] && bounds.contains(marker.getPosition())) {
671
+ keys.push(idx);
672
+ ignore[idx] = true;
673
+ }
674
+ });
675
+ }
676
+
677
+ hash = keys.join('-');
678
+ currentHashes[hash] = true;
679
+
680
+ if (overlays[hash]) { // hash is already set
681
+ return;
682
+ }
683
+
684
+ currentMarkers = keys.map(function (key) {
685
+ return markers[key];
686
+ });
687
+
688
+ // ask the user callback on this subset (may be composed by only one marker)
689
+ overlayOptions = options.cb(slice(currentMarkers));
690
+
691
+ // create an overlay if cb returns its properties
692
+ if (overlayOptions) {
693
+ bounds = gmElement('LatLngBounds');
694
+ foreach(currentMarkers, function (marker) {
695
+ bounds.extend(marker.getPosition());
696
+ if (marker.getMap()) {
697
+ marker.setMap(null);
698
+ }
699
+ });
700
+
701
+ overlayOptions = dupOpts(overlayOptions);
702
+ overlayOptions.position = bounds.getCenter();
703
+ overlays[hash] = new ClusterOverlay(self, slice(currentMarkers), createOverlayView(map, overlayOptions), bounds);
704
+ newOverlays.push(overlays[hash]);
705
+
706
+ } else {
707
+ foreach(currentMarkers, function (marker) {
708
+ if (!marker.getMap()) { // to avoid marker blinking
709
+ marker.setMap(map);
710
+ }
711
+ });
712
+ }
713
+
714
+ });
715
+
716
+ // remove previous overlays
717
+ foreach(objectKeys(overlays), function (key) {
718
+ if (!currentHashes[key]) {
719
+ overlays[key].overlay.setMap(null);
720
+ delete overlays[key];
721
+ }
722
+ });
723
+
724
+ if (newOverlays.length) {
725
+ foreach(handlers, function (fn) {
726
+ fn(newOverlays);
727
+ });
728
+ }
729
+ }
730
+
731
+ /**
732
+ * Restart redraw timer
733
+ */
734
+ function delayRedraw() {
735
+ clearTimeout(timer);
736
+ timer = setTimeout(redraw, 100);
737
+ }
738
+
739
+ /**
740
+ * Init clustering
741
+ */
742
+ function init() {
743
+ gm.event.addListener(map, "zoom_changed", delayRedraw);
744
+ gm.event.addListener(map, "bounds_changed", delayRedraw);
745
+ redraw();
746
+ }
747
+
748
+ igniter = setInterval(function () {
749
+ projection = getProjection(map);
750
+ if (projection) {
751
+ clearInterval(igniter);
752
+ init();
753
+ }
754
+ }, 10);
755
+ }
756
+
757
+ /**
758
+ * Configure google maps loading library
759
+ * @param {string|object} options
760
+ */
761
+ $.gmap3 = function (options) {
762
+ loadOptions = options;
763
+ };
764
+
765
+ /**
766
+ * jQuery Plugin
767
+ */
768
+ $.fn.gmap3 = function (options) {
769
+ var items = [];
770
+ gm = window.google.maps; // once gmap3 is loaded, google.maps library should be loaded
771
+ this.each(function () {
772
+ var $this = $(this), gmap3 = $this.data("gmap3");
773
+ if (!gmap3) {
774
+ gmap3 = new Gmap3($this, options);
775
+ $this.data("gmap3", gmap3);
776
+ }
777
+ items.push(gmap3);
778
+ });
779
+
780
+ return new Handler(this, items);
781
+ };
782
+
783
+ /**
784
+ * Class Handler
785
+ * Chainable objet which handle all Gmap3 items associated to all jQuery elements in the current command set
786
+ * @param {jQuery} chain - "this" to return to maintain the jQuery chain
787
+ * @param {Gmap3[]} items
788
+ * @constructor
789
+ */
790
+ function Handler(chain, items) {
791
+ var self = this;
792
+
793
+ // Map all functions from Gmap3 class
794
+ objectKeys(items[0]).forEach(function (name) {
795
+ self[name] = function () {
796
+ var results = [],
797
+ args = slice(arguments);
798
+ items.forEach(function (item) {
799
+ results.push(item[name].apply(item, args));
800
+ });
801
+ return name === 'get' ? (results.length > 1 ? results : results[0]) : self;
802
+ };
803
+ });
804
+
805
+ self.$ = chain;
806
+ }
807
+
808
+ /**
809
+ * Class Gmap3
810
+ * Handle a Google.maps.Map instance
811
+ * @param {jQuery} $container - Element to display the map in
812
+ * @param {Object} options - MapOptions
813
+ * @constructor
814
+ */
815
+ function Gmap3($container, options) {
816
+ var map,
817
+ previousResults = [],
818
+ promise = when(),
819
+ self = this;
820
+
821
+ function context() {
822
+ return {
823
+ $: $container,
824
+ get: self.get
825
+ };
826
+ }
827
+
828
+ /**
829
+ * Attach events to instances
830
+ * @param {Object } events
831
+ * @param {Array|Object} instances
832
+ * @param {array} [args] arguments to add
833
+ * @param {Boolean} once
834
+ */
835
+ function attachEvents(events, instances, args, once) {
836
+ var hasArgs = arguments.length > 3;
837
+ if (!hasArgs) {
838
+ once = args;
839
+ }
840
+ $.each(events, function (eventName, handlers) {
841
+ foreach(instances, function (instance) {
842
+ var isClusterOverlay = instance instanceof ClusterOverlay;
843
+ var isDom = isClusterOverlay || (instance instanceof gm.OverlayView);
844
+ var eventListener = isDom ? instance.$.get(0) : instance;
845
+ gm.event['add' + (isDom ? 'Dom' : '') + 'Listener' + (once ? 'Once' : '')](eventListener, eventName, function (event) {
846
+ foreach(handlers, function (handler) {
847
+ if (isFunction(handler)) {
848
+ if (isClusterOverlay) {
849
+ handler.call(context(), undefined /* marker */, instance, instance.cluster, event);
850
+ } else if (hasArgs) {
851
+ var buffer = slice(args);
852
+ buffer.unshift(instance);
853
+ buffer.push(event);
854
+ handler.apply(context(), buffer);
855
+ } else {
856
+ handler.call(context(), instance, event);
857
+ }
858
+ }
859
+ });
860
+ });
861
+ });
862
+ });
863
+ }
864
+
865
+ /**
866
+ * Decorator to handle multiple call based on array of options
867
+ * @param {Function} fn
868
+ * @returns {Deferred}
869
+ */
870
+ function multiple(fn) {
871
+ return function (options) {
872
+ if (isArray(options)) {
873
+ var instances = [];
874
+ var promises = options.map(function (opts) {
875
+ return fn.call(self, opts).then(function (instance) {
876
+ instances.push(instance);
877
+ });
878
+ });
879
+ return all(promises).then(function () {
880
+ previousResults.push(instances);
881
+ return instances;
882
+ });
883
+ } else {
884
+ return fn.apply(self, arguments).then(function (instance) {
885
+ previousResults.push(instance);
886
+ return instance;
887
+ });
888
+ }
889
+ };
890
+ }
891
+
892
+ /**
893
+ * Decorator to chain promise result onto the main promise chain
894
+ * @param {Function} fn
895
+ * @returns {Deferred}
896
+ */
897
+ function chainToPromise(fn) {
898
+ return function () {
899
+ var args = slice(arguments);
900
+ promise = promise.then(function (instance) {
901
+ if (isFunction(args[0])) {
902
+ // handle return as a deferred / promise to support both sync & async result
903
+ return when(args[0].call(context(), instance)).then(function (value) {
904
+ args[0] = value;
905
+ return fn.apply(self, args);
906
+ });
907
+ }
908
+
909
+ return when(fn.apply(self, args));
910
+ });
911
+ return promise;
912
+ };
913
+ }
914
+
915
+ self.map = chainToPromise(function (options) {
916
+ return map || resolveLatLng(options, 'center', function (opts) {
917
+ map = gmElement('Map', $container.get(0), opts);
918
+ previousResults.push(map);
919
+ return map;
920
+ });
921
+ });
922
+
923
+ // Space separated string of : separated element
924
+ // (google.maps class name) : (latLng property name) : (add map - 0|1 - default = 1)
925
+ foreachStr('Marker:position Circle:center InfoWindow:position:0 Polyline:path Polygon:paths', function (item) {
926
+ item = item.split(':');
927
+ var property = item[1] || '';
928
+ self[item[0].toLowerCase()] = chainToPromise(multiple(function (options) {
929
+ return (property.match(/^path/) ? resolveArrayOfLatLng : resolveLatLng)(options, property, function (opts) {
930
+ if (item[2] !== '0') {
931
+ opts.map = map;
932
+ }
933
+ return gmElement(item[0], opts);
934
+ });
935
+ }));
936
+ });
937
+
938
+ foreachStr('TrafficLayer TransitLayer BicyclingLayer', function (item) {
939
+ self[item.toLowerCase()] = chainToPromise(function () {
940
+ var instance = gmElement(item);
941
+ previousResults.push(instance);
942
+ instance.setMap(map);
943
+ return instance;
944
+ });
945
+ });
946
+
947
+ self.kmllayer = chainToPromise(multiple(function (options) {
948
+ options = dupOpts(options);
949
+ options.map = map;
950
+ return when(gmElement('KmlLayer', options));
951
+ }));
952
+
953
+ self.rectangle = chainToPromise(multiple(function (options) {
954
+ return resolveLatLngBounds(options, function (opts) {
955
+ opts.map = map;
956
+ return gmElement('Rectangle', opts);
957
+ });
958
+ }));
959
+
960
+ self.overlay = chainToPromise(multiple(function (options) {
961
+ function fn(opts) {
962
+ return createOverlayView(map, opts);
963
+ }
964
+
965
+ options = dupOpts(options);
966
+ return options.bounds ? resolveLatLngBounds(options, fn) : resolveLatLng(options, 'position', fn);
967
+ }));
968
+
969
+ self.groundoverlay = chainToPromise(function (url, bounds, options) {
970
+ return resolveLatLngBounds({bounds: bounds}, function (opts) {
971
+ options = dupOpts(options);
972
+ options.map = map;
973
+ var instance = gmElement('GroundOverlay', url, opts.bounds, options);
974
+ previousResults.push(instance);
975
+ return instance;
976
+ });
977
+ });
978
+
979
+ self.styledmaptype = chainToPromise(function (styleId, styles, options) {
980
+ var instance = gmElement('StyledMapType', styles, options);
981
+ previousResults.push(instance);
982
+ map.mapTypes.set(styleId, instance);
983
+ return instance;
984
+ });
985
+
986
+ self.streetviewpanorama = chainToPromise(function (container, options) {
987
+ return resolveLatLng(options, 'position', function (opts) {
988
+ var instance = gmElement('StreetViewPanorama', $(container).get(0), opts);
989
+ map.setStreetView(instance);
990
+ previousResults.push(instance);
991
+ return instance;
992
+ });
993
+ });
994
+
995
+ self.route = chainToPromise(function (options) {
996
+ var dfd = deferred();
997
+ options = dupOpts(options);
998
+ options.origin = toLatLng(options.origin);
999
+ options.destination = toLatLng(options.destination);
1000
+ service('DirectionsService').route(options, function (results, status) {
1001
+ previousResults.push(results);
1002
+ dfd.resolve(status === gm.DirectionsStatus.OK ? results : false);
1003
+ });
1004
+ return dfd;
1005
+ });
1006
+
1007
+ self.cluster = chainToPromise(function (options) {
1008
+ var cluster = new Cluster(map, dupOpts(options));
1009
+ previousResults.push(cluster);
1010
+ return resolved(cluster);
1011
+ });
1012
+
1013
+ self.directionsrenderer = chainToPromise(function (options) {
1014
+ var instance;
1015
+ if (options) {
1016
+ options = dupOpts(options);
1017
+ options.map = map;
1018
+ if (options.panel) {
1019
+ options.panel = $(options.panel).get(0);
1020
+ }
1021
+ instance = gmElement('DirectionsRenderer', options);
1022
+ }
1023
+ previousResults.push(instance);
1024
+ return instance;
1025
+ });
1026
+
1027
+ self.latlng = chainToPromise(multiple(function (options) {
1028
+ return resolveLatLng(options, 'latlng', function (opts) {
1029
+ previousResults.push(opts.latlng);
1030
+ return opts.latlng;
1031
+ });
1032
+ }));
1033
+
1034
+ self.fit = chainToPromise(function () {
1035
+ var bounds = gmElement('LatLngBounds');
1036
+ foreach(previousResults, function (instances) {
1037
+ if (instances !== map) {
1038
+ foreach(instances, function (instance) {
1039
+ if (instance) {
1040
+ if (instance.getPosition && instance.getPosition()) {
1041
+ bounds.extend(instance.getPosition());
1042
+ } else if (instance.getBounds && instance.getBounds()) {
1043
+ bounds.extend(instance.getBounds().getNorthEast());
1044
+ bounds.extend(instance.getBounds().getSouthWest());
1045
+ } else if (instance.getPaths && instance.getPaths()) {
1046
+ foreach(instance.getPaths().getArray(), function (path) {
1047
+ foreach(path.getArray(), function (latLng) {
1048
+ bounds.extend(latLng);
1049
+ });
1050
+ });
1051
+ } else if (instance.getPath && instance.getPath()) {
1052
+ foreach(instance.getPath().getArray(), function (latLng) {
1053
+ bounds.extend(latLng);
1054
+ });
1055
+ } else if (instance.getCenter && instance.getCenter()) {
1056
+ bounds.extend(instance.getCenter());
1057
+ }
1058
+ }
1059
+ });
1060
+ }
1061
+ });
1062
+ if (!bounds.isEmpty()) {
1063
+ map.fitBounds(bounds);
1064
+ }
1065
+ return true;
1066
+ });
1067
+
1068
+ self.wait = function (duration) {
1069
+ promise = promise.then(function (instance) {
1070
+ var dfd = deferred();
1071
+ setTimeout(function () {
1072
+ dfd.resolve(instance);
1073
+ }, duration);
1074
+ return dfd;
1075
+ });
1076
+ };
1077
+
1078
+ self.then = function (fn) {
1079
+ if (isFunction(fn)) {
1080
+ promise = promise.then(function (instance) {
1081
+ return when(fn.call(context(), instance)).then(function (newInstance) {
1082
+ return isUndefined(newInstance) ? instance : newInstance;
1083
+ });
1084
+ });
1085
+ }
1086
+ };
1087
+
1088
+ foreachStr('on once', function (name, once) {
1089
+ self[name] = function () {
1090
+ var events = arguments[0];
1091
+ if (events) {
1092
+ if (typeof events === 'string') { // cast call on('click', handler) to on({click: handler})
1093
+ events = {};
1094
+ events[arguments[0]] = slice(arguments, 1);
1095
+ }
1096
+ promise.then(function (instances) {
1097
+ if (instances) {
1098
+ if (instances instanceof Cluster) {
1099
+ instances._b(function (items) {
1100
+ if (items && items.length) {
1101
+ attachEvents(events, items, once);
1102
+ }
1103
+ });
1104
+ return attachEvents(events, instances.markers(), [undefined, instances], once);
1105
+ }
1106
+ attachEvents(events, instances, once);
1107
+ }
1108
+ });
1109
+ }
1110
+ };
1111
+ });
1112
+
1113
+ self.get = function (index) {
1114
+ if (isUndefined(index)) {
1115
+ return previousResults.map(function (instance) {
1116
+ return isArray(instance) ? instance.slice() : instance;
1117
+ });
1118
+ } else {
1119
+ if (index < 0) {
1120
+ index = previousResults.length + index;
1121
+ }
1122
+ return isArray(previousResults[index]) ? previousResults[index].slice() : previousResults[index];
1123
+ }
1124
+ };
1125
+
1126
+ if (options) {
1127
+ self.map(options);
1128
+ }
1129
+ }
1130
+
1131
+ })(jQuery, window, document);
js/jquery.addfields.js CHANGED
@@ -5,14 +5,14 @@ jQuery(document).ready(function ($) {
5
var newNum = new Number(num + 1); // the numeric ID of the new input field being added
6
// create the new element via clone(), and manipulate it's ID using newNum value
7
var newElem = $('#event' + num).clone().attr('id', 'event' + newNum);
8
- // manipulate the name/id values of the input inside the new element
9
// insert the new element after the last "duplicatable" input field
10
$('#event' + num).after(newElem);
11
// enable the "remove" button
12
$('#del_field').removeAttr('disabled');
13
// business rule: you can only add 10 occurrences
14
- if (newNum == 20)
15
$('#add_field').attr('disabled', 'disabled');
16
});
17
18
$('#del_field').on('click', function () {
@@ -21,38 +21,11 @@ jQuery(document).ready(function ($) {
21
// enable the "add" button
22
$('#add_field').removeAttr('disabled');
23
// if only one element remains, disable the "remove" button
24
- if (num - 1 == 1)
25
$('#del_field').attr('disabled', 'disabled');
26
$('#event_span').hide();
27
});
28
$('#del_field').attr('disabled', 'disabled');
29
$('#event_span').hide();
30
- });
31
-
32
- jQuery(document).ready(function ($) {
33
- $('#add_price').on('click', function () {
34
- var num = $('.clonedPrice').length; // how many "duplicatable" input fields we currently have
35
- var newNum = new Number(num + 1); // the numeric ID of the new input field being added
36
- // create the new element via clone(), and manipulate it's ID using newNum value
37
- var newElem = $('#price' + num).clone().attr('id', 'price' + newNum);
38
- // manipulate the name/id values of the input inside the new element
39
- // insert the new element after the last "duplicatable" input field
40
- $('#price' + num).after(newElem);
41
- // enable the "remove" button
42
- $('#del_price').removeAttr('disabled');
43
- // business rule: you can only add 6 variations
44
- if (newNum == 6)
45
- $('#add_price').attr('disabled', 'disabled');
46
- });
47
-
48
- $('#del_price').on('click', function () {
49
- var num = $('.clonedPrice').length; // how many "duplicatable" input fields we currently have
50
- $('#price' + num).remove(); // remove the last element
51
- // enable the "add" button
52
- $('#add_price').removeAttr('disabled');
53
- // if only one element remains, disable the "remove" button
54
- if (num - 1 == 1)
55
- $('#del_price').attr('disabled', 'disabled');
56
- });
57
- $('#del_price').attr('disabled', 'disabled');
58
});
5
var newNum = new Number(num + 1); // the numeric ID of the new input field being added
6
// create the new element via clone(), and manipulate it's ID using newNum value
7
var newElem = $('#event' + num).clone().attr('id', 'event' + newNum);
8
// insert the new element after the last "duplicatable" input field
9
$('#event' + num).after(newElem);
10
// enable the "remove" button
11
$('#del_field').removeAttr('disabled');
12
// business rule: you can only add 10 occurrences
13
+ if ( newNum == 20 ) {
14
$('#add_field').attr('disabled', 'disabled');
15
+ }
16
});
17
18
$('#del_field').on('click', function () {
21
// enable the "add" button
22
$('#add_field').removeAttr('disabled');
23
// if only one element remains, disable the "remove" button
24
+ if ( num - 1 == 1 ) {
25
$('#del_field').attr('disabled', 'disabled');
26
+ }
27
$('#event_span').hide();
28
});
29
$('#del_field').attr('disabled', 'disabled');
30
$('#event_span').hide();
31
});
js/mc-ajax.js CHANGED
@@ -6,7 +6,7 @@
6
var link = $(this).attr('href');
7
var height = $('.mc-main.calendar' ).height();
8
var ref = $(this).attr('data-rel');
9
- $('#' + ref).html('<div class=\"loading\" style=\"height:' + height + 'px\"><span>Loading...</span></div>');
10
$('#' + ref).load(link + ' #' + ref + ' > *', function () {
11
$('.calendar-event').children().not('.event-title').hide();
12
$('#' + ref).attr('tabindex', '-1').focus();
@@ -17,7 +17,7 @@
17
var link = $(this).attr('href');
18
var ref = $(this).attr('data-rel');
19
var height = $('.mc-main.list' ).height();
20
- $('#' + ref).html('<div class=\"loading\" style=\"height:' + height + 'px\"><span>Loading...</span></div>');
21
$('#' + ref).load(link + ' #' + ref + ' > *', function () {
22
$('li.mc-events').children().not('.event-date').hide();
23
$('li.current-day').children().show();
@@ -29,7 +29,7 @@
29
var link = $(this).attr('href');
30
var ref = $(this).attr('data-rel');
31
var height = $('.mc-main.mini' ).height();
32
- $('#' + ref).html('<div class=\"loading\" style=\"height:' + height + 'px\"><span>Loading...</span></div>');
33
$('#' + ref).load(link + ' #' + ref + ' > *', function () {
34
$('.mini .has-events').children().not('.trigger, .mc-date, .event-date').hide();
35
$('#' + ref).attr('tabindex', '-1').focus();
6
var link = $(this).attr('href');
7
var height = $('.mc-main.calendar' ).height();
8
var ref = $(this).attr('data-rel');
9
+ $('#' + ref).html('<div class=\"mc-loading\"></div><div class=\"loading\" style=\"height:' + height + 'px\"><span class="screen-reader-text">Loading...</span></div>');
10
$('#' + ref).load(link + ' #' + ref + ' > *', function () {
11
$('.calendar-event').children().not('.event-title').hide();
12
$('#' + ref).attr('tabindex', '-1').focus();
17
var link = $(this).attr('href');
18
var ref = $(this).attr('data-rel');
19
var height = $('.mc-main.list' ).height();
20
+ $('#' + ref).html('<div class=\"mc-loading\"></div><div class=\"loading\" style=\"height:' + height + 'px\"><span class="screen-reader-text">Loading...</span></div>');
21
$('#' + ref).load(link + ' #' + ref + ' > *', function () {
22
$('li.mc-events').children().not('.event-date').hide();
23
$('li.current-day').children().show();
29
var link = $(this).attr('href');
30
var ref = $(this).attr('data-rel');
31
var height = $('.mc-main.mini' ).height();
32
+ $('#' + ref).html('<div class=\"mc-loading\"></div><div class=\"loading\" style=\"height:' + height + 'px\"><span class="screen-reader-text">Loading...</span></div>');
33
$('#' + ref).load(link + ' #' + ref + ' > *', function () {
34
$('.mini .has-events').children().not('.trigger, .mc-date, .event-date').hide();
35
$('#' + ref).attr('tabindex', '-1').focus();
js/mc-quicktags.js DELETED
@@ -1 +0,0 @@
1
- edButtons[edButtons.length] = new edButton('My Calendar', 'My Calendar', '[my_calendar]', '', '');
js/tabs.js CHANGED
@@ -20,4 +20,14 @@ jQuery(document).ready(function ($) {
20
});
21
}
22
});
23
});
20
});
21
}
22
});
23
+
24
+ $( '#mc-generator .custom' ).hide();
25
+ $( '#mc-generator select[name=type]' ).on( 'change', function () {
26
+ var selected = $( this ).val();
27
+ if ( selected == 'custom' ) {
28
+ $( '#mc-generator .custom' ).show();
29
+ } else {
30
+ $( '#mc-generator .custom' ).hide();
31
+ }
32
+ });
33
});
my-calendar-api.php CHANGED
@@ -355,15 +355,4 @@ function mc_generate_alert_ical( $alarm ) {
355
$alert .= "END:VALARM";
356
357
return $alert;
358
- }
359
-
360
- function mc_flatten_event_array( $events ) {
361
- $flat = array();
362
- foreach( $events as $event ) {
363
- foreach( $event as $e ) {
364
- $flat[] = $e;
365
- }
366
- }
367
-
368
- return $flat;
369
}
355
$alert .= "END:VALARM";
356
357
return $alert;
358
}
my-calendar-behaviors.php CHANGED
@@ -101,24 +101,24 @@ function edit_my_calendar_behaviors() {
101
<label
102
for="calendar-js"><?php _e( 'Calendar Behaviors: Grid View', 'my-calendar' ); ?></label><br/><textarea
103
id="calendar-js" name="mc_caljs" rows="12"
104
- cols="80"><?php echo esc_js( $mc_caljs ); ?></textarea>
105
</p>
106
<p>
107
<label
108
for="list-js"><?php _e( 'Calendar Behaviors: List View', 'my-calendar' ); ?></label><br/><textarea
109
id="list-js" name="mc_listjs" rows="12"
110
- cols="80"><?php echo esc_js( $mc_listjs ); ?></textarea>
111
</p>
112
<p>
113
<label for="mini-js"><?php _e( 'Calendar Behaviors: Mini Calendar View', 'my-calendar' ); ?></label><br/><textarea
114
id="mini-js" name="mc_minijs" rows="12"
115
- cols="80"><?php echo esc_js( $mc_minijs ); ?></textarea>
116
</p>
117
<p>
118
<label
119
for="ajax-js"><?php _e( 'Calendar Behaviors: AJAX', 'my-calendar' ); ?></label><br/><textarea
120
id="ajax-js" name="mc_ajaxjs" rows="12"
121
- cols="80"><?php echo esc_js( $mc_ajaxjs ); ?></textarea>
122
</p>
123
<?php } ?>
124
<p>
101
<label
102
for="calendar-js"><?php _e( 'Calendar Behaviors: Grid View', 'my-calendar' ); ?></label><br/><textarea
103
id="calendar-js" name="mc_caljs" rows="12"
104
+ cols="80"><?php echo esc_textarea( $mc_caljs ); ?></textarea>
105
</p>
106
<p>
107
<label
108
for="list-js"><?php _e( 'Calendar Behaviors: List View', 'my-calendar' ); ?></label><br/><textarea
109
id="list-js" name="mc_listjs" rows="12"
110
+ cols="80"><?php echo esc_textarea( $mc_listjs ); ?></textarea>
111
</p>
112
<p>
113
<label for="mini-js"><?php _e( 'Calendar Behaviors: Mini Calendar View', 'my-calendar' ); ?></label><br/><textarea
114
id="mini-js" name="mc_minijs" rows="12"
115
+ cols="80"><?php echo esc_textarea( $mc_minijs ); ?></textarea>
116
</p>
117
<p>
118
<label
119
for="ajax-js"><?php _e( 'Calendar Behaviors: AJAX', 'my-calendar' ); ?></label><br/><textarea
120
id="ajax-js" name="mc_ajaxjs" rows="12"
121
+ cols="80"><?php echo esc_textarea( $mc_ajaxjs ); ?></textarea>
122
</p>
123
<?php } ?>
124
<p>
my-calendar-categories.php CHANGED
@@ -3,6 +3,15 @@ if ( ! defined( 'ABSPATH' ) ) {
3
exit;
4
} // Exit if accessed directly
5
6
// Function to handle the management of categories
7
8
// This is a hack for people who don't have PHP installed with exif_imagetype
@@ -89,7 +98,7 @@ function my_calendar_manage_categories() {
89
}
90
91
if ( isset( $_POST['mode'] ) && $_POST['mode'] == 'add' ) {
92
- $term = wp_insert_term( wp_kses_post( $_POST['category_name'] ), 'mc-event-category' );
93
if ( ! is_wp_error( $term ) ) {
94
$term = $term['term_id'];
95
} else {
@@ -103,7 +112,7 @@ function my_calendar_manage_categories() {
103
'category_term' => $term
104
);
105
106
- $add = array_map( 'wp_kses_post', $add );
107
108
109
// actions and filters
@@ -249,12 +258,7 @@ function mc_edit_category_form( $view = 'edit', $catID = '' ) {
249
} ?>"/>
250
</div>
251
<?php } ?>
252
- <fieldset>
253
- <legend><?php if ( $view == 'add' ) {
254
- _e( 'Add Category', 'my-calendar' );
255
- } else {
256
- _e( 'Edit Category', 'my-calendar' );
257
- } ?></legend><?php
258
if ( ! empty( $cur_cat ) && is_object( $cur_cat ) ) {
259
$color = ( strpos( $cur_cat->category_color, '#' ) !== 0 ) ? '#' : '';
260
$color .= $cur_cat->category_color;
@@ -277,7 +281,7 @@ function mc_edit_category_form( $view = 'edit', $catID = '' ) {
277
class="mc-color-input"
278
size="10"
279
maxlength="7"
280
- value="<?php echo esc_attr( $color ); ?>"/>
281
</li>
282
<li>
283
<label for="cat_icon"><?php _e( 'Category Icon', 'my-calendar' ); ?></label> <select
@@ -302,19 +306,21 @@ function mc_edit_category_form( $view = 'edit', $catID = '' ) {
302
} ?>
303
<?php $checked = ( $view == 'add' ) ? '' : mc_is_checked( 'mc_default_category', $cur_cat->category_id, '', true ); ?>
304
<?php $holiday_checked = ( $view == 'add' ) ? '' : mc_is_checked( 'mc_skip_holidays_category', $cur_cat->category_id, '', true ); ?>
305
<input type="checkbox" value="on" name="category_private"
306
id="cat_private"<?php echo $private_checked; ?> /> <label
307
- for="cat_private"><?php _e( 'Private category (logged-in users only)', 'my-calendar' ); ?></label>
308
- <input type="checkbox" value="on" name="mc_default_category"
309
id="mc_default_category"<?php echo $checked; ?> /> <label
310
- for="mc_default_category"><?php _e( 'Default category', 'my-calendar' ); ?></label>
311
- <input type="checkbox" value="on" name="mc_skip_holidays_category"
312
id="mc_shc"<?php echo $holiday_checked; ?> /> <label
313
- for="mc_shc"><?php _e( 'Holiday Category', 'my-calendar' ); ?></label>
314
</li>
315
<?php echo apply_filters( 'mc_category_fields', '', $cur_cat ); ?>
316
</ul>
317
- </fieldset>
318
<p>
319
<input type="submit" name="save" class="button-primary"
320
value="<?php if ( $view == 'add' ) {
@@ -400,18 +406,18 @@ function mc_manage_categories() {
400
?>
401
<tr class="<?php echo $class; ?>">
402
<th scope="row"><?php echo $cat->category_id; ?></th>
403
- <td><?php echo stripslashes( wp_kses_post( $cat->category_name ) );
404
if ( $cat->category_id == get_option( 'mc_default_category' ) ) {
405
- echo ' ' . __( '(Default)' );
406
}
407
if ( $cat->category_id == get_option( 'mc_skip_holidays_category' ) ) {
408
- echo ' ' . __( '(Holiday)' );
409
} ?></td>
410
- <td style="background-color:<?php echo $background; ?>;color: <?php echo $foreground; ?>"><?php echo ( $icon_src ) ? "<img src='$icon_src' alt='' />" : ''; ?> <?php echo $background; ?></td>
411
<td><?php echo ( $cat->category_private == 1 ) ? __( 'Yes', 'my-calendar' ) : __( 'No', 'my-calendar' ); ?></td>
412
<td><a
413
href="<?php echo admin_url( "admin.php?page=my-calendar-categories&amp;mode=edit&amp;category_id=$cat->category_id" ); ?>"
414
- class='edit'><?php _e( 'Edit', 'my-calendar' ); ?></a></td><?php
415
if ( $cat->category_id == 1 ) {
416
echo '<td>' . __( 'N/A', 'my-calendar' ) . '</td>';
417
} else {
@@ -419,7 +425,7 @@ function mc_manage_categories() {
419
<td><a
420
href="<?php echo admin_url( "admin.php?page=my-calendar-categories&amp;mode=delete&amp;category_id=$cat->category_id" ); ?>"
421
class="delete"
422
- onclick="return confirm('<?php _e( 'Are you sure you want to delete this category?', 'my-calendar' ); ?>')"><?php _e( 'Delete', 'my-calendar' ); ?></a>
423
</td><?php
424
} ?>
425
</tr><?php
@@ -428,4 +434,60 @@ function mc_manage_categories() {
428
} else {
429
echo '<p>' . __( 'There are no categories in the database - or something has gone wrong!', 'my-calendar' ) . '</p>';
430
}
431
}
3
exit;
4
} // Exit if accessed directly
5
6
+
7
+ function mc_update_category( $field, $data, $category ) {
8
+ global $wpdb;
9
+ $field = sanitize_key( $field );
10
+ $result = $wpdb->query( $wpdb->prepare( "UPDATE " . my_calendar_categories_table() . " SET $field = %d WHERE category_id=%d", $data, $category ) );
11
+
12
+ return $result;
13
+ }
14
+
15
// Function to handle the management of categories
16
17
// This is a hack for people who don't have PHP installed with exif_imagetype
98
}
99
100
if ( isset( $_POST['mode'] ) && $_POST['mode'] == 'add' ) {
101
+ $term = wp_insert_term( strip_tags( $_POST['category_name'] ), 'mc-event-category' );
102
if ( ! is_wp_error( $term ) ) {
103
$term = $term['term_id'];
104
} else {
112
'category_term' => $term
113
);
114
115
+ $add = array_map( 'mc_kses_post', $add );
116
117
118
// actions and filters
258
} ?>"/>
259
</div>
260
<?php } ?>
261
+ <?php
262
if ( ! empty( $cur_cat ) && is_object( $cur_cat ) ) {
263
$color = ( strpos( $cur_cat->category_color, '#' ) !== 0 ) ? '#' : '';
264
$color .= $cur_cat->category_color;
281
class="mc-color-input"
282
size="10"
283
maxlength="7"
284
+ value="<?php echo ( $color != '#' ) ? esc_attr( $color ) : ''; ?>"/>
285
</li>
286
<li>
287
<label for="cat_icon"><?php _e( 'Category Icon', 'my-calendar' ); ?></label> <select
306
} ?>
307
<?php $checked = ( $view == 'add' ) ? '' : mc_is_checked( 'mc_default_category', $cur_cat->category_id, '', true ); ?>
308
<?php $holiday_checked = ( $view == 'add' ) ? '' : mc_is_checked( 'mc_skip_holidays_category', $cur_cat->category_id, '', true ); ?>
309
+ <ul class='checkboxes'>
310
+ <li>
311
<input type="checkbox" value="on" name="category_private"
312
id="cat_private"<?php echo $private_checked; ?> /> <label
313
+ for="cat_private"><?php _e( 'Private category (logged-in users only)', 'my-calendar' ); ?></label></li>
314
+ <li><input type="checkbox" value="on" name="mc_default_category"
315
id="mc_default_category"<?php echo $checked; ?> /> <label
316
+ for="mc_default_category"><?php _e( 'Default category', 'my-calendar' ); ?></label></li>
317
+ <li><input type="checkbox" value="on" name="mc_skip_holidays_category"
318
id="mc_shc"<?php echo $holiday_checked; ?> /> <label
319
+ for="mc_shc"><?php _e( 'Holiday Category', 'my-calendar' ); ?></label></li>
320
+ </ul>
321
</li>
322
<?php echo apply_filters( 'mc_category_fields', '', $cur_cat ); ?>
323
</ul>
324
<p>
325
<input type="submit" name="save" class="button-primary"
326
value="<?php if ( $view == 'add' ) {
406
?>
407
<tr class="<?php echo $class; ?>">
408
<th scope="row"><?php echo $cat->category_id; ?></th>
409
+ <td><?php echo stripslashes( mc_kses_post( $cat->category_name ) );
410
if ( $cat->category_id == get_option( 'mc_default_category' ) ) {
411
+ echo ' <strong>' . __( '(Default)' ) . '</strong>';
412
}
413
if ( $cat->category_id == get_option( 'mc_skip_holidays_category' ) ) {
414
+ echo ' <strong>' . __( '(Holiday)' ) . '</strong>';
415
} ?></td>
416
+ <td style="background-color:<?php echo $background; ?>;color: <?php echo $foreground; ?>"><?php echo ( $icon_src ) ? "<img src='$icon_src' alt='' />" : ''; ?> <?php echo ( $background != '#' ) ? $background : ''; ?></td>
417
<td><?php echo ( $cat->category_private == 1 ) ? __( 'Yes', 'my-calendar' ) : __( 'No', 'my-calendar' ); ?></td>
418
<td><a
419
href="<?php echo admin_url( "admin.php?page=my-calendar-categories&amp;mode=edit&amp;category_id=$cat->category_id" ); ?>"
420
+ class='edit'><?php printf( __( 'Edit %s', 'my-calendar' ), '<span class="screen-reader-text">' . stripslashes( mc_kses_post( $cat->category_name ) ) . '</span>' ); ?></a></td><?php
421
if ( $cat->category_id == 1 ) {
422
echo '<td>' . __( 'N/A', 'my-calendar' ) . '</td>';
423
} else {
425
<td><a
426
href="<?php echo admin_url( "admin.php?page=my-calendar-categories&amp;mode=delete&amp;category_id=$cat->category_id" ); ?>"
427
class="delete"
428
+ onclick="return confirm('<?php _e( 'Are you sure you want to delete this category?', 'my-calendar' ); ?>')"><?php printf( __( 'Delete %s', 'my-calendar' ), '<span class="screen-reader-text">' . stripslashes( mc_kses_post( $cat->category_name ) ) . '</span>' ); ?></a>
429
</td><?php
430
} ?>
431
</tr><?php
434
} else {
435
echo '<p>' . __( 'There are no categories in the database - or something has gone wrong!', 'my-calendar' ) . '</p>';
436
}
437
+ }
438
+
439
+
440
+ add_action( 'show_user_profile', 'mc_profile' );
441
+ add_action( 'edit_user_profile', 'mc_profile' );
442
+ add_action( 'profile_update', 'mc_save_profile' );
443
+ /**
444
+ * Show user profile data on Edit User pages.
445
+ *
446
+ * return @string Configuration forms for My Calendar user-specific settings.
447
+ */
448
+ function mc_profile() {
449
+ global $user_ID;
450
+ $current_user = wp_get_current_user();
451
+ $user_edit = ( isset( $_GET['user_id'] ) ) ? (int) $_GET['user_id'] : $user_ID;
452
+
453
+ if ( user_can( $user_edit, 'mc_manage_events' ) && current_user_can( 'manage_options' ) ) {
454
+ $permissions = (array) get_user_meta( $user_edit, 'mc_user_permissions', true );
455
+ $selected = ( empty( $permissions ) || in_array( 'all', $permissions ) ) ? ' selected="selected"' : '';
456
+ ?>
457
+ <h3><?php _e( 'My Calendar Editor Permissions', 'my-calendar' ); ?></h3>
458
+ <table class="form-table">
459
+ <tr>
460
+ <th scope="row">
461
+ <label for="mc_user_permissions"><?php _e( "Allowed Categories", 'my-calendar' ); ?></label>
462
+ </th>
463
+ <td>
464
+ <select name="mc_user_permissions[]" id="mc_user_permissions" multiple>
465
+ <option value='all'<?php echo $selected; ?>><?php _e( 'All', 'my-calendar' ); ?></option>
466
+ <?php echo mc_category_select( $permissions, true, 'multiple' ); ?>
467
+ </select>
468
+ </td>
469
+ </tr>
470
+ <?php echo apply_filters( 'mc_user_fields', '', $user_edit ); ?>
471
+ </table>
472
+ <?php
473
+ }
474
+ }
475
+
476
+ /**
477
+ * Save user profile data
478
+ */
479
+ function mc_save_profile() {
480
+ global $user_ID;
481
+ $current_user = wp_get_current_user();
482
+ if ( isset( $_POST['user_id'] ) ) {
483
+ $edit_id = (int) $_POST['user_id'];
484
+ } else {
485
+ $edit_id = $user_ID;
486
+ }
487
+ if ( current_user_can( 'manage_options' ) ) {
488
+ $mc_user_permission = $_POST['mc_user_permissions'];
489
+ update_user_meta( $edit_id, 'mc_user_permissions', $mc_user_permission );
490
+ }
491
+
492
+ apply_filters( 'mc_save_user', $edit_id, $_POST );
493
}
my-calendar-core.php CHANGED
@@ -34,8 +34,12 @@ if ( ! function_exists( 'is_ssl' ) ) {
34
}
35
}
36
37
- // mod from Mike T
38
function my_calendar_getUsers() {
39
global $blog_id;
40
$count = count_users( 'time' );
41
$args = array(
@@ -51,7 +55,7 @@ function my_calendar_getUsers() {
51
52
function mc_selected_users( $selected ) {
53
$selected = explode( ',', $selected );
54
- $users = my_calendar_getUsers();
55
$options = '';
56
foreach ( $users as $u ) {
57
if ( in_array( $u->ID, $selected ) ) {
@@ -60,7 +64,7 @@ function mc_selected_users( $selected ) {
60
$checked = '';
61
}
62
$display_name = ( $u->display_name == '' ) ? $u->user_nicename : $u->display_name;
63
- $options = '<option value="' . $u->ID . '"' . $checked . ">$display_name</option>\n";
64
}
65
66
return $options;
@@ -168,11 +172,14 @@ function mc_register_styles() {
168
$css_array = ( get_option( 'mc_show_css' ) != '' ) ? explode( ",", get_option( 'mc_show_css' ) ) : array();
169
// check whether any scripts are actually enabled.
170
if ( get_option( 'mc_calendar_javascript' ) != 1 || get_option( 'mc_list_javascript' ) != 1 || get_option( 'mc_mini_javascript' ) != 1 || get_option( 'mc_ajax_javascript' ) != 1 ) {
171
- if ( @in_array( $id, $js_array ) || get_option( 'mc_show_js' ) == '' ) {
172
wp_enqueue_script( 'jquery' );
173
if ( get_option( 'mc_gmap' ) == 'true' ) {
174
- wp_enqueue_script( 'gmaps', "https://maps.google.com/maps/api/js?sensor=true" );
175
- wp_enqueue_script( 'gmap3', plugins_url( 'js/gmap3.min.js', __FILE__ ), array( 'jquery' ) );
176
}
177
}
178
}
@@ -206,7 +213,7 @@ function my_calendar_wp_head() {
206
if ( @in_array( $id, $array ) || get_option( 'mc_show_css' ) == '' ) {
207
// generate category colors
208
$category_styles = $inv = $type = $alt = '';