Co-Authors Plus - Version 2.5

Version Description

Download this release

Release Info

Developer batmoo
Plugin Icon wp plugin Co-Authors Plus
Version 2.5
Comparing to
See all releases

Code changes from version 2.1.1 to 2.5

admin.css DELETED
@@ -1,50 +0,0 @@
1
- #coauthors-table {
2
-
3
- }
4
- div.dbx-content #coauthors-table {
5
- width:100%;
6
- }
7
- #coauthors-table td {
8
- padding-left:0;
9
- padding-right:0;
10
- vertical-align: middle;
11
- background: #F9F9F9;
12
- }
13
- #coauthors-table td.select select {
14
- margin-top:0;
15
- }
16
- div.dbx-content #coauthors-table td.select select {
17
- width:100%;
18
- }
19
-
20
- span.author-tag {
21
- display:block;
22
- padding: 5px 3px;
23
- color: #21759B;
24
- border-bottom: 1px solid #21759B;
25
- cursor: text;
26
- }
27
- span.author-tag:hover {
28
- cursor: text;
29
- background: #EAF2FA ;
30
- color:#D54E21;
31
- }
32
- input.coauthor-suggest {
33
- margin:5px 0;
34
- color:#888;
35
- }
36
- input.coauthor-suggest:focus {
37
- color: #333;
38
- }
39
-
40
- span.delete-coauthor {
41
- padding: 3px;
42
- text-decoration: underline;
43
- color:#FF0000;
44
- font-size:11px;
45
- }
46
- span.delete-coauthor:hover {
47
- cursor: pointer;
48
- background: #FF0000;
49
- color: #fff;
50
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
admin.js DELETED
@@ -1,484 +0,0 @@
1
- jQuery(document).ready(function () {
2
-
3
- /*
4
- * Get an author's name from their ID
5
- * Iterates through the coauthors-select input box until finds the entry with the associated ID
6
- * @param int User ID
7
- *
8
- */
9
- /*
10
- function coauthors_get_author_name( authorID ) {
11
- var select = jQuery('#coauthors-select');
12
-
13
- if(authorID){
14
- //Find the provided author ID
15
- var name = "";
16
- select.find("option").each(function(i){
17
- if(this.value == authorID) {
18
- name = this.innerHTML;
19
- return;
20
- }
21
-
22
- });
23
- return name;
24
- }
25
- return false;
26
-
27
- }
28
- */
29
-
30
- /*
31
- * Selects author (specified by authorID) within the appropriate select box (specified by selectID)
32
- * @param int
33
- * @param string ID of the select box
34
- */
35
- function coauthors_select_author( author, selectID ) {
36
- if(!selectID) selectID = '#post_author_override';
37
-
38
- var select = jQuery(selectID);
39
-
40
- if(author.id){
41
- //Find the provided author ID
42
- select.find("option").each(function(i){
43
- if(this.value == author.id) {
44
- jQuery(this).attr('selected','true');
45
- // Fix to retain order of selected coauthors
46
- //jQuery(this).appendTo(select);
47
- return false;
48
- }
49
- });
50
- }
51
-
52
- }
53
-
54
- /*
55
- * Unselects author
56
- * @param string Name of the Author to remove
57
- */
58
- function coauthors_remove_author( authorName ) {
59
- var select = jQuery('#coauthors-select');
60
- if(authorName){
61
- //Find the provided author ID
62
- select.find("option").each(function(i){
63
- if(this.innerHTML == authorName) {
64
- jQuery(this).removeAttr('selected');
65
- return true;
66
- }
67
- });
68
- }
69
- }
70
-
71
- /*
72
- * Click handler for the delete button
73
- * @param event
74
- */
75
- var coauthors_delete_onclick = function(event){
76
-
77
- if(confirm(i18n.coauthors.confirm_delete)) {
78
- var tr = jQuery(this).parent().parent();
79
- var name = tr.find('span.author-tag').text();
80
- coauthors_remove_author(name);
81
- tr.remove();
82
-
83
- return true;
84
- }
85
- return false;
86
- };
87
-
88
- var coauthors_edit_onclick = function(event) {
89
- var tag = jQuery(event.currentTarget);
90
-
91
- var co = tag.prev();
92
-
93
- tag.hide();
94
- co.show()
95
- .focus()
96
- ;
97
-
98
- co.previousAuthor = tag.text();
99
- }
100
-
101
- /*
102
- * Save coauthor
103
- * @param int Author ID
104
- * @param string Author Name
105
- * @param object The autosuggest input box
106
- */
107
- function coauthors_save_coauthor(author, co) {
108
-
109
- coauthors_remove_author(co.next().text());
110
-
111
- // get sibling <span>, update, and hide
112
- co.next()
113
- .html(author.name)
114
- .show()
115
- .next()
116
- .val(author.login)
117
- ;
118
-
119
- // select new author
120
- if(co.attr('name')=='coauthors-main') {
121
- coauthors_select_author( author );
122
- }
123
-
124
- }
125
-
126
-
127
- /*
128
- * Add coauthor
129
- * @param int Author ID
130
- * @param string Author Name
131
- * @param object The autosuggest input box
132
- * @param boolean Initial set up or not?
133
- */
134
- // function coauthors_add_coauthor(authorID, authorName, co, init, count){
135
- function coauthors_add_coauthor(author, co, init, count){
136
-
137
- // Check if editing
138
- if(co && co.next().attr('class')=='author-tag') {
139
- coauthors_save_coauthor(author, co);
140
-
141
- } else {
142
- // Not editing, so we create a new author entry
143
- if(count == 0) {
144
- var coName = (count == 0) ? 'coauthors-main' : '';
145
- // Add new author to <select>
146
- coauthors_select_author( author );
147
- var options = {};
148
- } else {
149
- var options = { addDelete: true, addEdit: false };
150
- }
151
- // Create autosuggest box and text tag
152
- if(!co) var co = coauthors_create_autosuggest(author, coName)
153
- var tag = coauthors_create_author_tag(author);
154
- var input = coauthors_create_author_hidden_input(author);
155
-
156
- coauthors_add_to_table(co, tag, input, options);
157
-
158
- if(!init) {
159
- // Create new author-suggest and append it to a new row
160
- var newCO = coauthors_create_autosuggest('', false);
161
- coauthors_add_to_table(newCO);
162
- }
163
- }
164
-
165
- co.bind('blur', coauthors_stop_editing);
166
-
167
- // Set the value for the auto-suggest box to the Author's name and hide it
168
- co.val(unescape(author.name))
169
- .hide()
170
- .unbind('focus')
171
- ;
172
-
173
- return true;
174
- }
175
-
176
-
177
- /*
178
- * Add the autosuggest box and text tag to the Co-Authors table
179
- * @param object Autosuggest input box
180
- * @param object Text tag
181
- * @param
182
- */
183
- function coauthors_add_to_table( co, tag, input, options ) {
184
- if(co) {
185
- var td = jQuery('<td></td>')
186
- .addClass('suggest')
187
- .append(co)
188
- .append(tag)
189
- .append(input)
190
- ;
191
- var tr = jQuery('<tr></tr>');
192
- tr.append(td);
193
-
194
- //Add buttons to row
195
- if(tag) coauthors_insert_author_edit_cells(tr, options);
196
-
197
- jQuery('#coauthors-table').append(tr);
198
- }
199
- }
200
-
201
- /*
202
- * Adds a delete and edit button next to an author
203
- * @param object The row to which the new author should be added
204
- */
205
- function coauthors_insert_author_edit_cells(tr, options){
206
-
207
- var td = jQuery('<td></td>')
208
- .addClass('coauthors-author-options')
209
- ;
210
-
211
- /*
212
- if(options.addEdit) {
213
- var editBtn = jQuery('<span></span>')
214
- .addClass('edit-coauthor')
215
- .text(i18n.coauthors.edit_label)
216
- .bind('click', coauthors_edit_onclick)
217
- ;
218
- td.append(editBtn);
219
- }
220
- */
221
- if(options.addDelete) {
222
- var deleteBtn = jQuery('<span></span>')
223
- .addClass('delete-coauthor')
224
- .text(i18n.coauthors.delete_label)
225
- .bind('click', coauthors_delete_onclick)
226
- ;
227
- td.append(deleteBtn);
228
- }
229
-
230
- jQuery(tr).append(td);
231
- return tr;
232
-
233
- }
234
-
235
- /*
236
- * Creates autosuggest input box
237
- * @param string [optional] Name of the author
238
- * @param string [optional] Name to be applied to the input box
239
- */
240
- function coauthors_create_autosuggest(authorName, inputName) {
241
-
242
- if(!inputName) inputName = 'coauthorsinput[]';
243
-
244
- var co = jQuery('<input/>')
245
- .attr({
246
- 'class': 'coauthor-suggest',
247
- 'name': inputName
248
- })
249
- .appendTo(div)
250
-
251
- .suggest(coauthor_ajax_suggest_link,{
252
- onSelect:
253
- function() {
254
-
255
- var vals = this.value.split("|");
256
-
257
- var author = {}
258
- author.id = jQuery.trim(vals[0]);
259
- author.login = jQuery.trim(vals[1]);
260
- author.name = jQuery.trim(vals[2]);
261
-
262
- if(author.id=="New") {
263
- //alert('Eventually, this will allow you to add a new author right from here. But it\'s not ready yet. *sigh*');
264
- coauthors_new_author_display(name);
265
- } else {
266
- //coauthors_add_coauthor(login, name, co);
267
- coauthors_add_coauthor(author, co);
268
- }
269
- }
270
- })
271
- .keydown(function(e) {
272
- if(e.keyCode == 13) {return false;}
273
-
274
- })
275
- ;
276
-
277
- if(authorName)
278
- co.attr('value', unescape(authorName));
279
- else
280
- co.attr('value', i18n.coauthors.search_box_text)
281
- .focus(function(){co.val('')})
282
- .blur(function(){co.val(i18n.coauthors.search_box_text)})
283
- ;
284
-
285
- return co;
286
-
287
- }
288
-
289
- /*
290
- * Blur handler for autosuggest input box
291
- * @param event
292
- */
293
- function coauthors_stop_editing(event) {
294
-
295
- var co = jQuery(event.target);
296
- var tag = jQuery(co.next());
297
-
298
- co.attr('value',tag.text());
299
-
300
- co.hide();
301
- tag.show();
302
-
303
- // editing = false;
304
- }
305
-
306
- /*
307
- * Creates the text tag for an author
308
- * @param string Name of the author
309
- */
310
- function coauthors_create_author_tag (author) {
311
-
312
- var tag = jQuery('<span></span>')
313
- .html(unescape(author.name))
314
- .attr('title', i18n.coauthors.input_box_title)
315
- .addClass('author-tag')
316
- // Add Click event to edit
317
- .click(coauthors_edit_onclick);
318
- return tag;
319
- }
320
-
321
- /*
322
- * Creates the text tag for an author
323
- * @param string Name of the author
324
- */
325
- function coauthors_create_author_hidden_input (author) {
326
- var input = jQuery('<input />')
327
- .attr({
328
- 'type': 'hidden',
329
- 'id': 'coauthors_hidden_input',
330
- 'name': 'coauthors[]',
331
- 'value': unescape(author.login)
332
- })
333
- ;
334
-
335
- return input;
336
- }
337
-
338
-
339
- /*
340
- * Display form for creating new author
341
- * @param string Name of the author
342
- */
343
- function coauthors_new_author_display (name) {
344
-
345
- tb_show('Add New User', '?inlineId=awesome&modal=true');
346
-
347
- }
348
-
349
- /*
350
- * Creates display for adding new author
351
- * @param string Name of the author
352
- */
353
- function coauthors_new_author_create_display ( ) {
354
-
355
- var author_window = jQuery('<div></div>')
356
- .appendTo(jQuery('body'))
357
- .attr('id','new-author-window')
358
- .addClass('wrap')
359
- .append(
360
- jQuery('<div></div>')
361
- .addClass('icon32')
362
- .attr('id','icon-users')
363
- )
364
- .append(
365
- jQuery('<h2></h2>')
366
- .text('Add new author')
367
- .attr('id', 'add-new-user')
368
-
369
- )
370
- .append(
371
- jQuery('<div/>')
372
- .attr('id', 'createauthor-ajax-response')
373
- )
374
- ;
375
-
376
- var author_form = jQuery('<form />')
377
- .appendTo(author_window)
378
- .attr({
379
- id: 'createauthor',
380
- name: 'createauthor',
381
- method: 'post',
382
- action: ''
383
- })
384
- ;
385
-
386
-
387
-
388
- var create_text_field = function( name, id, label) {
389
-
390
- var field = jQuery('<input />')
391
- .attr({
392
- type:'text',
393
- name: name,
394
- id: id,
395
- })
396
- var label = jQuery('<label></label>')
397
- .attr('for',name)
398
- .text(label)
399
-
400
- //return {field, label};
401
-
402
- };
403
-
404
- create_field('user_login', 'user_login', 'User Name');
405
- create_field('first_name', 'first_name', 'First Name');
406
-
407
- //last_name
408
- //email
409
- //pass1
410
- //email password checkbox
411
- //role
412
- }
413
-
414
-
415
-
416
- if(jQuery('#post_author_override')){
417
-
418
- // Check if user has permissions to change post authors; if not, remove controls and end
419
- if(!coauthors_can_edit_others_posts){
420
- jQuery('#authordiv, #pageauthordiv').remove();
421
- return;
422
- }
423
-
424
- // Changes the meta_box title from "Post Author" to "Post Author(s)"
425
- var h3 = jQuery('#authordiv :header, #pageauthordiv :header').html(
426
- /page[^\/]+$/.test(window.location.href) ?
427
- i18n.coauthors.page_metabox_title
428
- :
429
- i18n.coauthors.post_metabox_title
430
- );
431
-
432
- // Add the controls to add co-authors
433
- var div = jQuery('#authordiv div, #pageauthordiv div').filter(function(){
434
- if(jQuery(this).is('.inside') || jQuery(this).is('.dbx-content'))
435
- return true;
436
- return false;
437
- })[0];
438
-
439
- if(div){
440
-
441
- // Create the co-authors table
442
- var table = jQuery('<table></table>')
443
- .attr('id', 'coauthors-table')
444
- ;
445
- var coauthors_table = jQuery('<tbody></tbody>')
446
- .appendTo(table)
447
- ;
448
- var tr = jQuery('<tr></tr>');
449
- var td = jQuery('<td></td>')
450
- .addClass('select')
451
- ;
452
-
453
- var select = jQuery('#post_author_override')[0];
454
-
455
- td.append(select);
456
- tr.append(td);
457
- coauthors_table.append(tr);
458
- jQuery(div).append(table);
459
-
460
- // Hide original dropdown box
461
- jQuery('#post_author_override').hide();
462
-
463
- // Show help text
464
- var help = jQuery('<p></p>').html(i18n.coauthors.help_text);
465
- jQuery('#authordiv .inside').append(help);
466
- jQuery('#pageauthordiv .inside').append(help);
467
-
468
- }
469
-
470
- // Select authors already added to the post
471
- var addedAlready = [];
472
- //jQuery('#the-list tr').each(function(){
473
- var count = 0;
474
- jQuery.each(post_coauthors, function() {
475
- coauthors_add_coauthor(this, undefined, true, count );
476
- count++;
477
- });
478
-
479
- // Create new author-suggest and append it to a new row
480
- var newCO = coauthors_create_autosuggest('', false);
481
- coauthors_add_to_table(newCO);
482
- }
483
-
484
- });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
co-authors-plus.php ADDED
@@ -0,0 +1,897 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /*
3
+ Plugin Name: Co-Authors Plus
4
+ Plugin URI: http://wordpress.org/extend/plugins/co-authors-plus/
5
+ Description: Allows multiple authors to be assigned to a post. This plugin is an extended version of the Co-Authors plugin developed by Weston Ruter.
6
+ Version: 2.5
7
+ Author: Mohammad Jangda
8
+ Author URI: http://digitalize.ca
9
+ Copyright: Some parts (C) 2009-2011, Mohammad Jangda; Other parts (C) 2008, Weston Ruter
10
+
11
+ GNU General Public License, Free Software Foundation <http://creativecommons.org/licenses/GPL/2.0/>
12
+ This program is free software; you can redistribute it and/or modify
13
+ it under the terms of the GNU General Public License as published by
14
+ the Free Software Foundation; either version 2 of the License, or
15
+ (at your option) any later version.
16
+
17
+ This program is distributed in the hope that it will be useful,
18
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
19
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20
+ GNU General Public License for more details.
21
+
22
+ You should have received a copy of the GNU General Public License
23
+ along with this program; if not, write to the Free Software
24
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
25
+
26
+ */
27
+
28
+ if( ! defined( 'COAUTHORS_PLUS_DEBUG' ) )
29
+ define( 'COAUTHORS_PLUS_DEBUG', false );
30
+
31
+ if( ! defined( 'COAUTHORS_DEFAULT_BEFORE' ) )
32
+ define( 'COAUTHORS_DEFAULT_BEFORE', '' );
33
+
34
+ if( ! defined( 'COAUTHORS_DEFAULT_BETWEEN' ) )
35
+ define( 'COAUTHORS_DEFAULT_BETWEEN', ', ' );
36
+
37
+ if( ! defined( 'COAUTHORS_DEFAULT_BETWEEN_LAST' ) )
38
+ define( 'COAUTHORS_DEFAULT_BETWEEN_LAST', __( ', and ', 'co-authors-plus' ) );
39
+
40
+ if( ! defined( 'COAUTHORS_DEFAULT_AFTER' ) )
41
+ define( 'COAUTHORS_DEFAULT_AFTER', '' );
42
+
43
+ define( 'COAUTHORS_PLUS_PATH', dirname( __FILE__ ) );
44
+ define( 'COAUTHORS_PLUS_URL', plugin_dir_url( __FILE__ ) );
45
+
46
+ define( 'COAUTHORS_PLUS_VERSION', '2.5' );
47
+
48
+ class coauthors_plus {
49
+
50
+ // Name for the taxonomy we're using to store coauthors
51
+ var $coauthor_taxonomy = 'author';
52
+ // Unique identified added as a prefix to all options
53
+ var $options_group = 'coauthors_plus_';
54
+ // Initially stores default option values, but when load_options is run, it is populated with the options stored in the WP db
55
+ var $options = array(
56
+ 'allow_subscribers_as_authors' => 0,
57
+ );
58
+
59
+ var $coreauthors_meta_box_name = 'authordiv';
60
+ var $coauthors_meta_box_name = 'coauthorsdiv';
61
+
62
+ var $gravatar_size = 25;
63
+
64
+ var $_pages_whitelist = array( 'post.php', 'post-new.php' );
65
+
66
+ /**
67
+ * __construct()
68
+ */
69
+ function __construct() {
70
+ global $pagenow;
71
+
72
+ // Load admin_init function
73
+ add_action( 'admin_init', array( $this,'admin_init' ) );
74
+
75
+ // Load plugin options
76
+ $this->load_options();
77
+
78
+ // Register new taxonomy so that we can store all our authors
79
+ register_taxonomy( $this->coauthor_taxonomy, 'post', array('hierarchical' => false, 'update_count_callback' => '_update_post_term_count', 'label' => false, 'query_var' => false, 'rewrite' => false, 'sort' => true, 'show_ui' => false ) );
80
+
81
+ // Modify SQL queries to include coauthors
82
+ add_filter( 'posts_where', array( $this, 'posts_where_filter' ) );
83
+ add_filter( 'posts_join', array( $this, 'posts_join_filter' ) );
84
+ add_filter( 'posts_groupby', array( $this, 'posts_groupby_filter' ) );
85
+
86
+ // Action to set users when a post is saved
87
+ add_action( 'save_post', array( $this, 'coauthors_update_post' ), 10, 2 );
88
+ // Filter to set the post_author field when wp_insert_post is called
89
+ add_filter( 'wp_insert_post_data', array( $this, 'coauthors_set_post_author_field' ) );
90
+
91
+ // Action to reassign posts when a user is deleted
92
+ add_action( 'delete_user', array( $this, 'delete_user_action' ) );
93
+
94
+ add_filter( 'get_usernumposts', array( $this, 'filter_count_user_posts' ), 10, 2 );
95
+
96
+ // Action to set up author auto-suggest
97
+ add_action( 'wp_ajax_coauthors_ajax_suggest', array( $this, 'ajax_suggest' ) );
98
+
99
+ // Filter to allow coauthors to edit posts
100
+ add_filter( 'user_has_cap', array( $this, 'add_coauthor_cap' ), 10, 3 );
101
+
102
+ add_filter( 'comment_notification_headers', array( $this, 'notify_coauthors' ), 10, 3 );
103
+
104
+ // Add the necessary pages for the plugin
105
+ add_action( 'admin_menu', array( $this, 'add_menu_items' ) );
106
+
107
+ // Handle the custom author meta box
108
+ add_action( 'add_meta_boxes', array( $this, 'add_coauthors_box' ) );
109
+ add_action( 'add_meta_boxes', array( $this, 'remove_authors_box' ) );
110
+
111
+ // Removes the author dropdown from the post quick edit
112
+ add_action( 'load-edit.php', array( $this, 'remove_quick_edit_authors_box' ) );
113
+
114
+ // Fix for author info not properly displaying on author pages
115
+ add_action( 'the_post', array( $this, 'fix_author_page' ) );
116
+ }
117
+
118
+ function coauthors_plus() {
119
+ $this->__construct();
120
+ }
121
+
122
+ /**
123
+ * Initialize the plugin for the admin
124
+ */
125
+ function admin_init() {
126
+ global $pagenow;
127
+
128
+ // Register all plugin settings so that we can change them and such
129
+ // Not really being used
130
+ /*
131
+ foreach( $this->options as $option => $value ) {
132
+ register_setting( $this->options_group, $this->get_plugin_option_fullname( $option ) );
133
+ }
134
+ */
135
+
136
+ // Hook into load to initialize custom columns
137
+ if( $this->is_valid_page() ) {
138
+ add_action( 'load-' . $pagenow, array( $this, 'admin_load_page' ) );
139
+ }
140
+
141
+ // Hooks to add additional coauthors to author column to Edit page
142
+ add_filter( 'manage_posts_columns', array( $this, '_filter_manage_posts_columns' ) );
143
+ add_filter( 'manage_pages_columns', array( $this, '_filter_manage_posts_columns' ) );
144
+ add_action( 'manage_posts_custom_column', array( $this, '_filter_manage_posts_custom_column' ) );
145
+ add_action( 'manage_pages_custom_column', array( $this, '_filter_manage_posts_custom_column' ) );
146
+ }
147
+
148
+ function admin_load_page() {
149
+
150
+ // Add the main JS script and CSS file
151
+ add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_scripts' ) );
152
+
153
+ // Add necessary JS variables
154
+ add_action( 'admin_print_scripts', array( $this, 'js_vars' ) );
155
+
156
+ }
157
+
158
+ /**
159
+ * Checks to see if the post_type supports authors
160
+ */
161
+ function authors_supported( $post_type ) {
162
+
163
+ if( ! function_exists( 'post_type_supports' ) && in_array( $post_type, array( 'post', 'page' ) ) )
164
+ return true;
165
+
166
+ // Hacky way to prevent issues on the Edit page
167
+ if( isset( $this->_edit_page_post_type_supported ) && $this->_edit_page_post_type_supported )
168
+ return true;
169
+
170
+ if( post_type_supports( $post_type, 'author' ) )
171
+ return true;
172
+
173
+ return false;
174
+ }
175
+
176
+ /**
177
+ * Gets the current global post type if one is set
178
+ */
179
+ function get_current_post_type() {
180
+ global $post, $typenow, $current_screen;
181
+
182
+ // "Cache" it!
183
+ if( isset( $this->_current_post_type ) )
184
+ return $this->_current_post_type;
185
+
186
+ if( $post && $post->post_type )
187
+ $post_type = $post->post_type;
188
+ elseif( $typenow )
189
+ $post_type = $typenow;
190
+ elseif( $current_screen && isset( $current_screen->post_type ) )
191
+ $post_type = $current_screen->post_type;
192
+ elseif( isset( $_REQUEST['post_type'] ) )
193
+ $post_type = sanitize_key( $_REQUEST['post_type'] );
194
+ else
195
+ $post_type = '';
196
+
197
+ if( $post_type )
198
+ $this->_current_post_type = $post_type;
199
+
200
+ return $post_type;
201
+ }
202
+
203
+ /**
204
+ * Removes the standard WordPress Author box.
205
+ * We don't need it because the Co-Authors one is way cooler.
206
+ */
207
+ function remove_authors_box() {
208
+
209
+ $post_type = $this->get_current_post_type();
210
+
211
+ if( $this->authors_supported( $post_type ) )
212
+ remove_meta_box( $this->coreauthors_meta_box_name, $post_type, 'normal' );
213
+ }
214
+
215
+ /**
216
+ * Adds a custom Authors box
217
+ */
218
+ function add_coauthors_box() {
219
+
220
+ $post_type = $this->get_current_post_type();
221
+
222
+ if( $this->authors_supported( $post_type ) && $this->current_user_can_set_authors() )
223
+ add_meta_box($this->coauthors_meta_box_name, __('Post Authors', 'co-authors-plus'), array( &$this, 'coauthors_meta_box' ), $post_type, 'normal', 'high');
224
+ }
225
+
226
+ /**
227
+ * Callback for adding the custom author box
228
+ */
229
+ function coauthors_meta_box( $post ) {
230
+ global $post;
231
+
232
+ $post_id = $post->ID;
233
+
234
+ if( !$post_id || $post_id == 0 || !$post->post_author )
235
+ $coauthors = array( wp_get_current_user() );
236
+ else
237
+ $coauthors = get_coauthors();
238
+
239
+ $count = 0;
240
+ if( !empty( $coauthors ) ) :
241
+ ?>
242
+ <div id="coauthors-readonly" class="hide-if-js1">
243
+ <ul>
244
+ <?php
245
+ foreach( $coauthors as $coauthor ) :
246
+ $count++;
247
+ ?>
248
+ <li>
249
+ <?php echo get_avatar( $coauthor->user_email, $this->gravatar_size ); ?>
250
+ <span id="coauthor-readonly-<?php echo $count; ?>" class="coauthor-tag">
251
+ <input type="text" name="coauthorsinput[]" readonly="readonly" value="<?php echo esc_attr( $coauthor->display_name ); ?>" />
252
+ <input type="text" name="coauthors[]" value="<?php echo esc_attr( $coauthor->user_login ); ?>" />
253
+ <input type="text" name="coauthorsemails[]" value="<?php echo esc_attr( $coauthor->user_email ); ?>" />
254
+ </span>
255
+ </li>
256
+ <?php
257
+ endforeach;
258
+ ?>
259
+ </ul>
260
+ <div class="clear"></div>
261
+ <p><?php _e( '<strong>Note:</strong> To edit post authors, please enable javascript or use a javascript-capable browser', 'co-authors-plus' ); ?></p>
262
+ </div>
263
+ <?php
264
+ endif;
265
+ ?>
266
+
267
+ <div id="coauthors-edit" class="hide-if-no-js">
268
+ <p><?php _e( 'Click on an author to change them. Click on <strong>Delete</strong> to remove them.', 'co-authors-plus' ); ?></p>
269
+ </div>
270
+
271
+ <?php wp_nonce_field( 'coauthors-edit', 'coauthors-nonce' ); ?>
272
+
273
+ <?php
274
+ }
275
+
276
+ /**
277
+ * Removes the author dropdown from the post quick edit
278
+ * It's a bit hacky, but the only way I can figure out :(
279
+ */
280
+ function remove_quick_edit_authors_box() {
281
+ $post_type = $this->get_current_post_type();
282
+ if( post_type_supports( $post_type, 'author' )) {
283
+ $this->_edit_page_post_type_supported = true;
284
+ remove_post_type_support( $post_type, 'author' );
285
+ }
286
+ }
287
+
288
+ /**
289
+ * Adds menu items for the plugin
290
+ */
291
+ function add_menu_items ( ) {
292
+ // Add sub-menu page for Custom statuses
293
+ //add_options_page(__( 'Co-Authors Plus', 'co-authors-plus' ), __( 'Co-Authors Plus', 'co-authors-plus' ), 'manage_options', 'co-authors-plus', array( $this, 'settings_page' ) );
294
+ }
295
+
296
+ /**
297
+ * Add coauthors to author column on edit pages
298
+ * @param array $post_columns
299
+ */
300
+ function _filter_manage_posts_columns($posts_columns) {
301
+ $new_columns = array();
302
+
303
+ foreach ($posts_columns as $key => $value) {
304
+ $new_columns[$key] = $value;
305
+ if( $key == 'title' )
306
+ $new_columns['coauthors'] = __( 'Authors', 'co-authors-plus' );
307
+
308
+ if ( $key == 'author' )
309
+ unset($new_columns[$key]);
310
+ }
311
+ return $new_columns;
312
+ } // END: _filter_manage_posts_columns
313
+
314
+ /**
315
+ * Insert coauthors into post rows on Edit Page
316
+ * @param string $column_name
317
+ **/
318
+ function _filter_manage_posts_custom_column($column_name) {
319
+ if ($column_name == 'coauthors') {
320
+ global $post;
321
+ $authors = get_coauthors( $post->ID );
322
+
323
+ $count = 1;
324
+ foreach( $authors as $author ) :
325
+ ?>
326
+ <a href="edit.php?author=<?php echo $author->ID; ?>"><?php echo $author->display_name ?></a><?php echo ( $count < count( $authors ) ) ? ',' : ''; ?>
327
+ <?php
328
+ $count++;
329
+ endforeach;
330
+ }
331
+ }
332
+
333
+ /**
334
+ * Modify the author query posts SQL to include posts co-authored
335
+ */
336
+ function posts_join_filter( $join ){
337
+ global $wpdb, $wp_query;
338
+
339
+ if( is_author() ){
340
+ // Check to see that JOIN hasn't already been added. Props michaelingp
341
+ $join_string = " INNER JOIN {$wpdb->term_relationships} ON ( {$wpdb->posts}.ID = {$wpdb->term_relationships}.object_id) INNER JOIN {$wpdb->term_taxonomy} ON ( {$wpdb->term_relationships}.term_taxonomy_id = {$wpdb->term_taxonomy}.term_taxonomy_id )";
342
+
343
+ if( strpos( $join, $join_string ) === false ) {
344
+ $join .= $join_string;
345
+ }
346
+ }
347
+
348
+ return $join;
349
+ }
350
+
351
+ /**
352
+ * Modify
353
+ */
354
+ function posts_where_filter( $where ){
355
+ global $wpdb, $wp_query;
356
+
357
+ if( is_author() ) {
358
+ $author = get_userdata( $wp_query->query_vars['author'] );
359
+ $term = get_term_by( 'name', $author->user_login, $this->coauthor_taxonomy );
360
+
361
+ if( $author ) {
362
+ $where = preg_replace( '/(\b(?:' . $wpdb->posts . '\.)?post_author\s*=\s*(\d+))/', '($1 OR (' . $wpdb->term_taxonomy . '.taxonomy = \''. $this->coauthor_taxonomy.'\' AND '. $wpdb->term_taxonomy .'.term_id = \''. $term->term_id .'\'))', $where, 1 ); #' . $wpdb->postmeta . '.meta_id IS NOT NULL AND
363
+
364
+ }
365
+ }
366
+ return $where;
367
+ }
368
+
369
+ /**
370
+ *
371
+ */
372
+ function posts_groupby_filter( $groupby ) {
373
+ global $wpdb;
374
+
375
+ if( is_author() ) {
376
+ $groupby = $wpdb->posts .'.ID';
377
+ }
378
+ return $groupby;
379
+ }
380
+
381
+ /**
382
+ * Filters post data before saving to db to set post_author
383
+ */
384
+ function coauthors_set_post_author_field( $data ) {
385
+ if ( !defined( 'DOING_AUTOSAVE' ) || !DOING_AUTOSAVE ) {
386
+ if( isset( $_REQUEST['coauthors-nonce'] ) && is_array( $_POST['coauthors'] ) ) {
387
+ $author = $_POST['coauthors'][0];
388
+ if( $author ) {
389
+ $author_data = get_user_by( 'login', $author );
390
+ $data['post_author'] = $author_data->ID;
391
+ }
392
+ } else {
393
+ // If for some reason we don't have the coauthors fields set
394
+ if( ! isset( $data['post_author'] ) ) {
395
+ $user = wp_get_current_user();
396
+ $data['post_author'] = $user->ID;
397
+ }
398
+ }
399
+ }
400
+ return $data;
401
+ }
402
+
403
+ /**
404
+ * Update a post's co-authors
405
+ * @param $post_ID
406
+ * @return
407
+ */
408
+ function coauthors_update_post( $post_id, $post ) {
409
+ $post_type = $post->post_type;
410
+
411
+ if ( !defined( 'DOING_AUTOSAVE' ) || !DOING_AUTOSAVE ) {
412
+
413
+ if( isset( $_REQUEST['coauthors-nonce'] ) ) {
414
+ check_admin_referer( 'coauthors-edit', 'coauthors-nonce' );
415
+
416
+ if( $this->current_user_can_set_authors() ){
417
+ $coauthors = array_map( 'sanitize_key', $_POST['coauthors'] );
418
+ return $this->add_coauthors( $post_id, $coauthors );
419
+ }
420
+ }
421
+ }
422
+ }
423
+
424
+ /**
425
+ * Add a user as coauthor for a post
426
+ */
427
+ function add_coauthors( $post_id, $coauthors, $append = false ) {
428
+ global $current_user;
429
+
430
+ $post_id = (int) $post_id;
431
+ $insert = false;
432
+
433
+ // if an array isn't returned, create one and populate with default author
434
+ if ( !is_array( $coauthors ) || 0 == count( $coauthors ) || empty( $coauthors ) ) {
435
+ $coauthors = array( $current_user->user_login );
436
+ }
437
+
438
+ // Add each co-author to the post meta
439
+ foreach( array_unique( $coauthors ) as $author ){
440
+
441
+ // Name and slug of term are the username;
442
+ $name = $author;
443
+
444
+ // Add user as a term if they don't exist
445
+ if( !term_exists( $name, $this->coauthor_taxonomy ) ) {
446
+ $args = array( 'slug' => sanitize_title( $name ) );
447
+ $insert = wp_insert_term( $name, $this->coauthor_taxonomy, $args );
448
+ }
449
+ }
450
+
451
+ // Add authors as post terms
452
+ if( !is_wp_error( $insert ) ) {
453
+ $set = wp_set_post_terms( $post_id, $coauthors, $this->coauthor_taxonomy, $append );
454
+ }
455
+ }
456
+
457
+ /**
458
+ * Action taken when user is deleted.
459
+ * - User term is removed from all associated posts
460
+ * - Option to specify alternate user in place for each post
461
+ * @param delete_id
462
+ */
463
+ function delete_user_action($delete_id){
464
+ global $wpdb;
465
+
466
+ $reassign_id = absint( $_POST['reassign_user'] );
467
+
468
+ // If reassign posts, do that -- use coauthors_update_post
469
+ if($reassign_id) {
470
+ // Get posts belonging to deleted author
471
+ $reassign_user = get_profile_by_id( 'user_login', $reassign_id );
472
+ // Set to new author
473
+ if( $reassign_user ) {
474
+ $post_ids = $wpdb->get_col( $wpdb->prepare( "SELECT ID FROM $wpdb->posts WHERE post_author = %d", $delete_id ) );
475
+
476
+ if ( $post_ids ) {
477
+ foreach ( $post_ids as $post_id ) {
478
+ $this->add_coauthors( $post_id, array( $reassign_user ), true );
479
+ }
480
+ }
481
+ }
482
+ }
483
+
484
+ $delete_user = get_profile_by_id( 'user_login', $delete_id );
485
+ if( $delete_user ) {
486
+ // Delete term
487
+ wp_delete_term( $delete_user, $this->coauthor_taxonomy );
488
+ }
489
+ }
490
+
491
+ function filter_count_user_posts( $count, $user_id ) {
492
+ $user = get_userdata( $user_id );
493
+
494
+ $term = get_term_by( 'slug', $user->user_login, $this->coauthor_taxonomy );
495
+
496
+ if( !$term || is_wp_error( ) ) {
497
+ $count = 0;
498
+ } else {
499
+ $count = $term->count;
500
+ }
501
+
502
+ return $count;
503
+ }
504
+
505
+ /**
506
+ * Checks to see if the current user can set authors or not
507
+ */
508
+ function current_user_can_set_authors( ) {
509
+ global $post, $typenow;
510
+
511
+ // TODO: Enable Authors to set Co-Authors
512
+
513
+ $post_type = $this->get_current_post_type();
514
+ // TODO: need to fix this; shouldn't just say no if don't have post_type
515
+ if( ! $post_type ) return false;
516
+
517
+ $post_type_object = get_post_type_object( $post_type );
518
+ $can_set_authors = current_user_can( $post_type_object->cap->edit_others_posts );
519
+
520
+ return apply_filters( 'coauthors_plus_edit_authors', $can_set_authors );
521
+ }
522
+
523
+ /**
524
+ * Fix for author info not properly displaying on author pages
525
+ *
526
+ * On an author archive, if the first story has coauthors and
527
+ * the first author is NOT the same as the author for the archive,
528
+ * the query_var is changed.
529
+ *
530
+ */
531
+ function fix_author_page( &$post ) {
532
+
533
+ if( is_author() ) {
534
+ global $wp_query, $authordata;
535
+
536
+ // Get the id of the author whose page we're on
537
+ $author_id = $wp_query->get( 'author' );
538
+
539
+ // Check that the the author matches the first author of the first post
540
+ if( $author_id != $authordata->ID ) {
541
+ // The IDs don't match, so we need to force the $authordata to the one we want
542
+ $authordata = get_userdata( $author_id );
543
+ }
544
+ }
545
+ }
546
+
547
+ /**
548
+ * Main function that handles search-as-you-type
549
+ */
550
+ function ajax_suggest() {
551
+ global $wpdb;
552
+
553
+ global $user_level;
554
+
555
+ if( ! isset( $_REQUEST['_wpnonce'] ) || ! wp_verify_nonce( $_REQUEST['_wpnonce'], 'coauthors-search' ) )
556
+ die();
557
+
558
+ if( empty( $_REQUEST['q'] ) )
559
+ die();
560
+
561
+ if( ! $this->current_user_can_set_authors() )
562
+ die();
563
+
564
+ $search = esc_html( strtolower( $_REQUEST['q'] ) );
565
+
566
+ $authors = $this->search_authors( $search );
567
+
568
+ foreach( $authors as $author ) {
569
+ echo $author->ID ." | ". $author->user_login ." | ". $author->display_name ." | ". $author->user_email ."\n";
570
+ }
571
+
572
+ if( COAUTHORS_PLUS_DEBUG ) {
573
+ echo 'queries:' . get_num_queries() ."\n";
574
+ echo 'timer: ' . timer_stop(1) . "sec\n";
575
+ }
576
+
577
+ die();
578
+
579
+ }
580
+
581
+ function search_authors( $search = '' ) {
582
+ $authors = array();
583
+ $author_fields = array( 'ID', 'display_name', 'user_login', 'user_email' );
584
+
585
+ if ( function_exists( 'get_users' ) ) {
586
+ $search = sprintf( '*%s*', $search ); // Enable wildcard searching
587
+ $authors = get_users( array( 'search' => $search, 'who' => 'authors', 'fields' => $author_fields ) );
588
+ } else {
589
+ // Pre 3.1 support
590
+ // This might not be the most eloquent way of doing things, but it's better than a nasty SQL query
591
+ $matching_authors = get_editable_authors( get_current_user_id() );
592
+
593
+ foreach( $matching_authors as $matching_author ) {
594
+ foreach( $author_fields as $author_field ) {
595
+ if( isset( $matching_author->$author_field ) && strpos( strtolower( $matching_author->$author_field ), $search ) !== false ) {
596
+ $authors[] = $matching_author;
597
+ break;
598
+ }
599
+ }
600
+ }
601
+ }
602
+
603
+ return (array) $authors;
604
+ }
605
+
606
+ /**
607
+ * Functions to add scripts and css
608
+ */
609
+ function enqueue_scripts($hook_suffix) {
610
+ global $pagenow, $post;
611
+
612
+ $post_type = $this->get_current_post_type();
613
+
614
+ // TODO: Check if user can set authors? $this->current_user_can_set_authors()
615
+ if( $this->is_valid_page() && $this->authors_supported( $post_type ) ) {
616
+
617
+ wp_enqueue_style( 'co-authors-plus-css', COAUTHORS_PLUS_URL . 'css/co-authors-plus.css', false, COAUTHORS_PLUS_VERSION, 'all' );
618
+ wp_enqueue_script( 'co-authors-plus-js', COAUTHORS_PLUS_URL . 'js/co-authors-plus.js', array('jquery', 'suggest'), COAUTHORS_PLUS_VERSION, true);
619
+
620
+ $js_strings = array(
621
+ 'edit_label' => __( 'Edit', 'co-authors-plus' ),
622
+ 'delete_label' => __( 'Delete', 'co-authors-plus' ),
623
+ 'confirm_delete' => __( 'Are you sure you want to delete this author?', 'co-authors-plus' ),
624
+ 'input_box_title' => __( 'Click to change this author', 'co-authors-plus' ),
625
+ 'search_box_text' => __( 'Search for an author', 'co-authors-plus' ),
626
+ 'help_text' => __( 'Click on an author to change them. Click on <strong>Delete</strong> to remove them.', 'co-authors-plus' ),
627
+ );
628
+ wp_localize_script( 'co-authors-plus-js', 'coAuthorsPlusStrings', $js_strings );
629
+
630
+ }
631
+ }
632
+
633
+ /**
634
+ * Adds necessary javascript variables to admin pages
635
+ */
636
+ function js_vars() {
637
+
638
+ $post_type = $this->get_current_post_type();
639
+
640
+ if( $this->is_valid_page() && $this->authors_supported( $post_type ) && $this->current_user_can_set_authors() ) {
641
+ ?>
642
+ <script type="text/javascript">
643
+ // AJAX link used for the autosuggest
644
+ var coAuthorsPlus_ajax_suggest_link = '<?php echo add_query_arg(
645
+ array(
646
+ 'action' => 'coauthors_ajax_suggest',
647
+ 'post_type' => $post_type,
648
+ ),
649
+ wp_nonce_url( 'admin-ajax.php', 'coauthors-search' )
650
+ ); ?>';
651
+ </script>
652
+ <?php
653
+ }
654
+ } // END: js_vars()
655
+
656
+ /**
657
+ * Helper to only add javascript to necessary pages. Avoids bloat in admin.
658
+ */
659
+ function is_valid_page() {
660
+ global $pagenow;
661
+
662
+ return in_array( $pagenow, $this->_pages_whitelist );
663
+ }
664
+
665
+ function get_post_id() {
666
+ global $post;
667
+ $post_id = 0;
668
+
669
+ if ( is_object( $post ) ) {
670
+ $post_id = $post->ID;
671
+ }
672
+
673
+ if( ! $post_id ) {
674
+ if ( isset( $_GET['post'] ) )
675
+ $post_id = (int) $_GET['post'];
676
+ elseif ( isset( $_POST['post_ID'] ) )
677
+ $post_id = (int) $_POST['post_ID'];
678
+ }
679
+
680
+ return $post_id;
681
+ }
682
+
683
+ /**
684
+ * Allows coauthors to edit the post they're coauthors of
685
+ * Pieces of code borrowed from http://pastebin.ca/1909968
686
+ *
687
+ */
688
+ function add_coauthor_cap( $allcaps, $caps, $args ) {
689
+
690
+ // Load the post data:
691
+ $user_id = isset( $args[1] ) ? $args[1] : 0;
692
+ $post_id = isset( $args[2] ) ? $args[2] : 0;
693
+
694
+ if( ! $post_id )
695
+ $post_id = $this->get_post_id();
696
+
697
+ if( ! $post_id )
698
+ return $allcaps;
699
+
700
+ $post = get_post( $post_id );
701
+
702
+ if( ! $post )
703
+ return $allcaps;
704
+
705
+ $post_type_object = get_post_type_object( $post->post_type );
706
+
707
+ // Bail out if we're not asking about a post
708
+ if ( ! in_array( $args[0], array( $post_type_object->cap->edit_post, $post_type_object->cap->edit_others_posts ) ) )
709
+ return $allcaps;
710
+
711
+ // Bail out for users who can already edit others posts
712
+ if ( isset( $allcaps[$post_type_object->cap->edit_others_posts] ) && $allcaps[$post_type_object->cap->edit_others_posts] )
713
+ return $allcaps;
714
+
715
+ // Bail out for users who can't publish posts if the post is already published
716
+ if ( 'publish' == $post->post_status && ( ! isset( $allcaps[$post_type_object->cap->publish_posts] ) || ! $allcaps[$post_type_object->cap->publish_posts] ) )
717
+ return $allcaps;
718
+
719
+ // Finally, double check that the user is a coauthor of the post
720
+ if( is_coauthor_for_post( $user_id, $post_id ) ) {
721
+ foreach($caps as $cap) {
722
+ $allcaps[$cap] = true;
723
+ }
724
+ }
725
+
726
+ return $allcaps;
727
+ }
728
+
729
+ /**
730
+ * Emails all coauthors when comment added instead of the main author
731
+ *
732
+ */
733
+ function notify_coauthors( $message_headers, $comment_id ) {
734
+ // TODO: this is broken!
735
+ $comment = get_comment($comment_id);
736
+ $post = get_post($comment->comment_post_ID);
737
+ $coauthors = get_coauthors($comment->comment_post_ID);
738
+
739
+ $message_headers .= 'cc: ';
740
+ $count = 0;
741
+ foreach($coauthors as $author) {
742
+ $count++;
743
+ if($author->ID != $post->post_author){
744
+ $message_headers .= $author->user_email;
745
+ if($count < count($coauthors)) $message_headers .= ',';
746
+ }
747
+ }
748
+ $message_headers .= "\n";
749
+ return $message_headers;
750
+ }
751
+
752
+ /**
753
+ * Loads options for the plugin.
754
+ * If option doesn't exist in database, it is added
755
+ *
756
+ * Note: default values are stored in the $this->options array
757
+ * Note: a prefix unique to the plugin is appended to all options. Prefix is stored in $this->options_group
758
+ */
759
+ function load_options ( ) {
760
+
761
+ $new_options = array();
762
+
763
+ foreach( $this->options as $option => $value ) {
764
+ $name = $this->get_plugin_option_fullname( $option );
765
+ $return = get_option( $name );
766
+ if( $return === false ) {
767
+ add_option( $name, $value );
768
+ $new_array[$option] = $value;
769
+ } else {
770
+ $new_array[$option] = $return;
771
+ }
772
+ }
773
+ $this->options = $new_array;
774
+
775
+ } // END: load_options
776
+
777
+
778
+ /**
779
+ * Returns option for the plugin specified by $name, e.g. custom_stati_enabled
780
+ *
781
+ * Note: The plugin option prefix does not need to be included in $name
782
+ *
783
+ * @param string name of the option
784
+ * @return option|null if not found
785
+ *
786
+ */
787
+ function get_plugin_option ( $name ) {
788
+ if( is_array( $this->options ) && $option = $this->options[$name] )
789
+ return $option;
790
+ else
791
+ return null;
792
+ } // END: get_option
793
+
794
+ // Utility function: appends the option prefix and returns the full name of the option as it is stored in the wp_options db
795
+ function get_plugin_option_fullname ( $name ) {
796
+ return $this->options_group . $name;
797
+ }
798
+
799
+ /**
800
+ * Adds Settings page for Edit Flow
801
+ */
802
+ function settings_page( ) {
803
+ global $wp_roles;
804
+
805
+ ?>
806
+ <div class="wrap">
807
+ <div class="icon32" id="icon-options-general"><br/></div>
808
+ <h2><?php _e('Co-Authors Plus', 'co-authors-plus') ?></h2>
809
+
810
+ <form method="post" action="options.php">
811
+ <?php settings_fields($this->options_group); ?>
812
+
813
+ <table class="form-table">
814
+ <tr valign="top">
815
+ <th scope="row"><strong><?php _e('Roles', 'co-authors-plus') ?></strong></th>
816
+ <td>
817
+ <p>
818
+ <label for="allow_subscribers_as_authors">
819
+ <?php /*<input type="checkbox" name="<?php echo $this->get_plugin_option_fullname('allow_subscribers_as_authors') ?>" value="1" <?php echo ($this->get_plugin_option('allow_subscribers_as_authors')) ? 'checked="checked"' : ''; ?> id="allow_subscribers_as_authors" /> <?php _e('Allow subscribers as authors', 'co-authors-plus') ?>*/ ?>
820
+ <input type="checkbox" disabled="disabled" name="<?php echo $this->get_plugin_option_fullname('allow_subscribers_as_authors') ?>" value="1" id="allow_subscribers_as_authors" /> <?php _e('Allow subscribers as authors', 'co-authors-plus') ?>
821
+ </label> <br />
822
+ <span class="description"><?php _e('Enabling this option will allow you to add users with the subscriber role as authors for posts.', 'co-authors-plus') ?></span>
823
+ <br />
824
+ <span class="description"><strong>Note:</strong> This option has been removed as of v2.5</span>
825
+ </p>
826
+ </td>
827
+ </tr>
828
+
829
+ </table>
830
+
831
+ <p class="submit">
832
+ <input type="submit" class="button-primary" value="<?php _e('Save Changes', 'co-authors-plus') ?>" />
833
+ </p>
834
+ </form>
835
+ </div>
836
+ <?php
837
+ }
838
+
839
+ function debug($msg, $object) {
840
+ if( COAUTHORS_PLUS_DEBUG ) {
841
+ echo '<hr />';
842
+ echo sprintf('<p>%s</p>', $msg);
843
+ echo '<pre>';
844
+ var_dump($object);
845
+ echo '</pre>';
846
+ }
847
+ }
848
+ }
849
+
850
+ /** Helper Functions **/
851
+
852
+ /**
853
+ * Replacement for the default WordPress get_profile function, since that doesn't allow for search by user_id
854
+ * Returns a the specified column value for the specified user
855
+ */
856
+ // TODO: Remove this function
857
+ if( ! function_exists( 'get_profile_by_id' ) ) {
858
+ function get_profile_by_id( $field, $user_id ) {
859
+ global $wpdb;
860
+
861
+ if( $field && $user_id )
862
+ return $wpdb->get_var( $wpdb->prepare( "SELECT $field FROM $wpdb->users WHERE ID = %d", $user_id ) );
863
+
864
+ return false;
865
+ }
866
+ }
867
+
868
+ function coauthors_plus_init() {
869
+
870
+ $plugin_dir = dirname( plugin_basename( __FILE__ ) ) . '/languages/';
871
+ load_plugin_textdomain( 'co-authors-plus', null, $plugin_dir );
872
+
873
+ // Check if we're running 3.0
874
+ if( function_exists( 'post_type_exists' ) ) {
875
+ // Create new instance of the coauthors_plus object
876
+ global $coauthors_plus;
877
+ $coauthors_plus = new coauthors_plus();
878
+
879
+ // Add template tags
880
+ require_once('template-tags.php');
881
+
882
+ } else {
883
+ // TODO: show error
884
+
885
+ }
886
+ }
887
+
888
+ /**
889
+ * Function to trigger actions when plugin is activated
890
+ */
891
+ function coauthors_plus_activate_plugin() {}
892
+
893
+ /** Let's get the plugin rolling **/
894
+ add_action( 'init', 'coauthors_plus_init' );
895
+
896
+ // Hook to perform action when plugin activated
897
+ register_activation_hook( __FILE__, 'coauthors_plus_activate_plugin' );
co-authors.php DELETED
@@ -1,648 +0,0 @@
1
- <?php
2
- /*
3
- Plugin Name: Co-Authors Plus
4
- Plugin URI: http://wordpress.org/extend/plugins/co-authors-plus/
5
- Description: Allows multiple authors to be assigned to a post. Co-authored posts appear on a co-author's posts page and feed. New template tags allow listing of co-authors. Editors may assign co-authors to a post via the 'Post Author' box. <em>This plugin is an extended version of the Co-Authors plugin originally developed at [Shepherd Interactive](http://www.shepherd-interactive.com/ "Shepherd Interactive specializes in web design and development in Portland, Oregon") (2007). Their plugin was inspired by 'Multiple Authors' plugin by Mark Jaquith (2005).</em>
6
- Version: 2.1.1
7
- Author: Mohammad Jangda
8
- Author URI: http://digitalize.ca
9
- Copyright: Some parts (C) 2009, Mohammad Jangda; Other parts (C) 2008, Weston Ruter, Shepherd Interactive
10
-
11
- GNU General Public License, Free Software Foundation <http://creativecommons.org/licenses/GPL/2.0/>
12
- This program is free software; you can redistribute it and/or modify
13
- it under the terms of the GNU General Public License as published by
14
- the Free Software Foundation; either version 2 of the License, or
15
- (at your option) any later version.
16
-
17
- This program is distributed in the hope that it will be useful,
18
- but WITHOUT ANY WARRANTY; without even the implied warranty of
19
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20
- GNU General Public License for more details.
21
-
22
- You should have received a copy of the GNU General Public License
23
- along with this program; if not, write to the Free Software
24
- Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
25
-
26
- */
27
-
28
- $plugin_dir = basename(dirname(__FILE__));
29
- load_plugin_textdomain( 'co-authors-plus','wp-content/plugins/'.$plugin_dir, $plugin_dir);
30
-
31
-
32
- define('COAUTHORS_FILE_PATH', '');
33
- define('COAUTHORS_DEFAULT_BEFORE', '');
34
- define('COAUTHORS_DEFAULT_BETWEEN', ', ');
35
- define('COAUTHORS_DEFAULT_BETWEEN_LAST', __(' and ', 'co-authors-plus'));
36
- define('COAUTHORS_DEFAULT_AFTER', '');
37
- define('COAUTHORS_PLUS_VERSION', '2.1');
38
-
39
- require_once('template-tags.php');
40
-
41
- class coauthors_plus {
42
-
43
- // Name for the taxonomy we're using to store coauthors
44
- var $coauthor_taxonomy = 'author';
45
- // Unique identified added as a prefix to all options
46
- var $options_group = 'coauthors_plus_';
47
- // Initially stores default option values, but when load_options is run, it is populated with the options stored in the WP db
48
- var $options = array(
49
- 'allow_subscribers_as_authors' => 0,
50
- );
51
-
52
- function __construct() {
53
- global $pagenow;
54
-
55
- // Load plugin options
56
- $this->load_options();
57
-
58
- // Register new taxonomy so that we can store all our authors
59
- if(!is_taxonomy($this->coauthor_taxonomy)) register_taxonomy( $this->coauthor_taxonomy, 'post', array('hierarchical' => false, 'update_count_callback' => '_update_post_term_count', 'label' => false, 'query_var' => false, 'rewrite' => false, 'sort' => true ) );
60
-
61
- // Modify SQL queries to include coauthors
62
- add_filter('posts_where', array(&$this, 'posts_where_filter'));
63
- add_filter('posts_join', array(&$this, 'posts_join_filter'));
64
- add_filter('posts_groupby', array(&$this, 'posts_groupby_filter'));
65
-
66
- // Hooks to add additional coauthors to author column to Edit Posts page
67
- if($pagenow == 'edit.php') {
68
- add_filter('manage_posts_columns', array(&$this, '_filter_manage_posts_columns'));
69
- add_action('manage_posts_custom_column', array(&$this, '_filter_manage_posts_custom_column'));
70
- }
71
-
72
- // Action to set users when a post is saved
73
- //add_action('edit_post', array(&$this, 'coauthors_update_post'));
74
- add_action('save_post', array(&$this, 'coauthors_update_post'));
75
-
76
- // Action to reassign posts when a user is deleted
77
- add_action('delete_user', array(&$this, 'delete_user_action'));
78
-
79
- // Action to set up author auto-suggest
80
- add_action('wp_ajax_coauthors_ajax_suggest', array(&$this, 'ajax_suggest') );
81
-
82
- // Filter to allow coauthors to edit posts
83
- add_filter('user_has_cap', array(&$this, 'add_coauthor_cap'), 10, 3 );
84
-
85
- add_filter('comment_notification_headers', array(&$this, 'notify_coauthors'), 10, 3);
86
-
87
- // Add the main JS script and CSS file
88
- if(get_bloginfo('version') >= 2.8) {
89
- // Using WordPress 2.8, are we?
90
- add_action('admin_enqueue_scripts', array(&$this, 'enqueue_scripts'));
91
- } else {
92
- // Pfft, you're old school
93
- add_action('admin_print_scripts', array(&$this, 'enqueue_scripts_legacy'));
94
- }
95
- // Add necessary JS variables
96
- add_action('admin_print_scripts', array(&$this, 'js_vars') );
97
-
98
- }
99
-
100
- function init() {
101
- //Add the necessary pages for the plugin
102
- add_action('admin_menu', array(&$this, 'add_menu_items'));
103
- }
104
-
105
- /**
106
- * Initialize the plugin for the admin
107
- */
108
- function admin_init() {
109
- // Register all plugin settings so that we can change them and such
110
- foreach($this->options as $option => $value) {
111
- register_setting($this->options_group, $this->get_plugin_option_fullname($option));
112
- }
113
- }
114
- /**
115
- * Function to trigger actions when plugin is activated
116
- */
117
- function activate_plugin() {}
118
-
119
- /**
120
- * Adds menu items for the plugin
121
- */
122
- function add_menu_items ( ) {
123
- // Add sub-menu page for Custom statuses
124
- add_options_page(__('Co-Authors Plus', 'co-authors-plus'), __('Co-Authors Plus', 'co-authors-plus'), 8, __FILE__, array(&$this, 'settings_page'));
125
- }
126
-
127
- /**
128
- * Add coauthors to author column on edit pages
129
- *
130
- * @param array $post_columns
131
- **/
132
- function _filter_manage_posts_columns($posts_columns) {
133
- $new_columns = array();
134
- foreach ($posts_columns as $key => $value) {
135
- $new_columns[$key] = $value;
136
- if ($key == 'author') {
137
- unset($new_columns[$key]);
138
- $new_columns['coauthors'] = __('Authors', 'co-authors-plus');
139
- }
140
- }
141
- return $new_columns;
142
- } // END: _filter_manage_posts_columns
143
-
144
- /**
145
- * Insert coauthors into post rows on Edit Page
146
- *
147
- * @param string $column_name
148
- **/
149
- function _filter_manage_posts_custom_column($column_name) {
150
- if ($column_name == 'coauthors') {
151
- global $post;
152
- $authors = get_coauthors($post->ID);
153
-
154
- $count = 1;
155
- foreach($authors as $author) :
156
- ?>
157
- <a href="edit.php?author=<?php echo $author->ID; ?>"><?php echo $author->display_name ?></a><?php echo ($count < count($authors)) ? ',' : ''; ?>
158
- <?php
159
- $count++;
160
- endforeach;
161
- }
162
- }
163
-
164
- /* Modify the author query posts SQL to include posts co-authored
165
- *
166
- */
167
- function posts_join_filter($join){
168
- global $wpdb,$wp_query;
169
-
170
- if(is_author()){
171
- $join .= " INNER JOIN $wpdb->term_relationships ON ($wpdb->posts.ID = $wpdb->term_relationships.object_id) INNER JOIN $wpdb->term_taxonomy ON ($wpdb->term_relationships.term_taxonomy_id = $wpdb->term_taxonomy.term_taxonomy_id)";
172
- }
173
- return $join;
174
- }
175
- /* Modify
176
- *
177
- */
178
- function posts_where_filter($where){
179
- global $wpdb, $wp_query;
180
-
181
- if(is_author()) {
182
- $author = get_userdata($wp_query->query_vars['author']);//get_profile( 'user_login', $wp_query->query_vars['author']);
183
- $term = get_term_by('name', $author->user_login, $this->coauthor_taxonomy);
184
-
185
- if($author) {
186
- $where = preg_replace('/(\b(?:' . $wpdb->posts . '\.)?post_author\s*=\s*(\d+))/', '($1 OR (' . $wpdb->term_taxonomy . '.taxonomy = \''. $this->coauthor_taxonomy.'\' AND '. $wpdb->term_taxonomy .'.term_id = \''. $term->term_id .'\'))', $where, 1); #' . $wpdb->postmeta . '.meta_id IS NOT NULL AND
187
-
188
- }
189
- }
190
- return $where;
191
- }
192
- /*
193
- *
194
- */
195
- function posts_groupby_filter($groupby){
196
- global $wpdb;
197
-
198
- if(is_author()) {
199
- $groupby = $wpdb->posts .'.ID';
200
- }
201
- return $groupby;
202
- }
203
-
204
-
205
- /* Update a post's co-authors
206
- * @param $postID
207
- * @return
208
- */
209
- function coauthors_update_post($post_ID){
210
- global $current_user;
211
-
212
- get_currentuserinfo();
213
-
214
- if($current_user->has_cap('edit_others_posts')){
215
- $coauthors = $_POST['coauthors'];
216
- return $this->add_coauthors($post_ID, $coauthors);
217
- }
218
- }
219
-
220
- /* Action taken when user is deleted.
221
- * - User term is removed from all associated posts
222
- * - Option to specify alternate user in place for each post
223
- * @param delete_id
224
- */
225
- function delete_user_action($delete_id){
226
- global $wpdb;
227
-
228
- $reassign_id = absint($_POST['reassign_user']);
229
-
230
- // If reassign posts, do that -- use coauthors_update_post
231
- if($reassign_id) {
232
- // Get posts belonging to deleted author
233
- $reassign_user = get_profile_by_id('user_login', $reassign_id);
234
- // Set to new author
235
- if($reassign_user) {
236
- $post_ids = $wpdb->get_col( $wpdb->prepare("SELECT ID FROM $wpdb->posts WHERE post_author = %d", $delete_id) );
237
-
238
- if ($post_ids) {
239
- foreach ($post_ids as $post_id) {
240
- $this->add_coauthors($post_id, array($reassign_user), true);
241
- }
242
- }
243
- }
244
- }
245
-
246
- $delete_user = get_profile_by_id('user_login', $delete_id);
247
- if($delete_user) {
248
- // Delete term
249
- wp_delete_term($delete_user, $this->coauthor_taxonomy);
250
- }
251
- }
252
-
253
- /* Add a user as coauthor for a post
254
- *
255
- */
256
- function add_coauthors( $post_ID, $coauthors, $append = false ) {
257
- global $current_user;
258
-
259
- $post_ID = (int) $post_ID;
260
-
261
- // if an array isn't returned, create one and populate with default author
262
- if (!is_array($coauthors) || 0 == count($coauthors) || empty($coauthors)) {
263
- // @TOOD: create option for default author
264
- $coauthors = array(get_option('default_author'));
265
- }
266
-
267
- // Add each co-author to the post meta
268
- foreach(array_unique($coauthors) as $author){
269
-
270
- // Name and slug of term are the username;
271
- $name = $author;
272
-
273
- // Add user as a term if they don't exist
274
- if(!is_term($name, $this->coauthor_taxonomy) ) {
275
- $args = array('slug' => sanitize_title($name) );
276
- $insert = wp_insert_term( $name, $this->coauthor_taxonomy, $args );
277
- }
278
- }
279
-
280
- // Add authors as post terms
281
- if(!is_wp_error($insert)) {
282
- $set = wp_set_post_terms( $post_ID, $coauthors, $this->coauthor_taxonomy, $append );
283
- } else {
284
- // @TODO: error
285
- }
286
-
287
- }
288
-
289
- /* Main function that handles search-as-you-type
290
- *
291
- */
292
- function ajax_suggest() {
293
- global $wpdb, $current_user;
294
-
295
- // Make sure that user is logged in; we don't want to enable direct access
296
- get_currentuserinfo();
297
- global $user_level;
298
-
299
- if($current_user->has_cap('edit_others_posts')) {
300
-
301
- // Set the minimum level of users to return
302
- if(!$this->get_plugin_option('allow_subscribers_as_authors')) {
303
- $user_level_where = "WHERE meta_key = '".$wpdb->prefix."user_level' AND meta_value >= 1";
304
- }
305
-
306
- // @TODO validate return
307
- $q = '%'.strtolower($_REQUEST["q"]).'%';
308
- if (!$q) return;
309
-
310
- $authors_query = $wpdb->prepare("SELECT DISTINCT u.ID, u.user_login, u.display_name, u.user_email FROM $wpdb->users AS u"
311
- ." INNER JOIN $wpdb->usermeta AS um ON u.ID = um.user_id"
312
- ." WHERE ID = ANY (SELECT user_id FROM $wpdb->usermeta $user_level_where)"
313
- ." AND (um.meta_key = 'first_name' OR um.meta_key = 'last_name' OR um.meta_key = 'nickname')"
314
- ." AND (u.user_login LIKE %s"
315
- ." OR u.user_nicename LIKE %s"
316
- ." OR u.display_name LIKE %s"
317
- ." OR u.user_email LIKE %s"
318
- ." OR um.meta_value LIKE %s)",$q,$q,$q,$q,$q);
319
-
320
- //echo $authors_query;
321
- $authors = $wpdb->get_results($authors_query, ARRAY_A);
322
-
323
- if(is_array($authors)) {
324
- foreach ($authors as $author) {
325
- echo $author['ID'] ." | ". $author['user_login']." | ". $author['display_name'] ." | ".$author['user_email'] ."\n";
326
- }
327
- }
328
- }
329
- die();
330
-
331
- }
332
-
333
- /* Functions to add scripts and css
334
- * enqueue_scripts is for 2.8+; enqueue_scripts_legacy for > 2.8
335
- */
336
- function enqueue_scripts($hook_suffix) {
337
- global $pagenow;
338
-
339
- if($this->is_valid_page()) {
340
- wp_enqueue_style('co-authors-plus', plugins_url('co-authors-plus/admin.css'), false, '', 'all');
341
- wp_enqueue_script('co-authors-plus', plugins_url('co-authors-plus/admin.js'), array('jquery', 'suggest', 'sack'), '', true);
342
- }
343
-
344
-
345
- //wp_dropdown_users(array('name' => 'ef_author', 'include' => $current_user->ID ));
346
-
347
- }
348
- function enqueue_scripts_legacy($hook_suffix) {
349
- global $pagenow;
350
-
351
- if($this->is_valid_page()) {
352
- //wp_enqueue_style('co-authors-plus', plugins_url('co-authors-plus/admin.css'), false, '', 'all');
353
- wp_enqueue_script('co-authors-plus', plugins_url('co-authors-plus/admin.js'), array('jquery', 'suggest', 'sack'), '');
354
- ?>
355
- <link type="text/css" rel="stylesheet" href="<?php echo plugins_url('co-authors-plus/admin.css') ?>" media="all" />
356
- <?php
357
- }
358
- }
359
-
360
- /* Adds necessary javascript variables to admin pages
361
- *
362
- */
363
- function js_vars() {
364
- global $current_user, $post_ID;
365
-
366
- get_currentuserinfo();
367
-
368
- if($this->is_valid_page()) {
369
- //wp_print_scripts( array( 'sack' ));
370
- $coauthors = get_coauthors( $post_ID );
371
- ?>
372
- <script type="text/javascript">
373
-
374
- // AJAX link used for the autosuggest
375
- var coauthor_ajax_suggest_link = "<?php echo 'admin-ajax.php?action=coauthors_ajax_suggest' ?>";
376
-
377
- if(!i18n || i18n == 'undefined') var i18n = {};
378
- i18n.coauthors = {};
379
-
380
- var coauthors_can_edit_others_posts = "<?php echo ($current_user->has_cap('edit_others_posts') ? 'true' : 'false')?>";
381
-
382
- i18n.coauthors.post_metabox_title = "<?php _e('Post Author(s)', 'co-authors-plus')?>";
383
- i18n.coauthors.page_metabox_title = "<?php _e('Page Author(s)', 'co-authors-plus')?>";
384
- i18n.coauthors.edit_label = "<?php _e('Edit', 'co-authors-plus')?>";
385
- i18n.coauthors.delete_label = "<?php _e('Delete', 'co-authors-plus')?>";
386
- i18n.coauthors.confirm_delete = "<?php _e('Are you sure you want to delete this author?', 'co-authors-plus')?>";
387
- i18n.coauthors.input_box_title = "<?php _e('Click to change this author', 'co-authors-plus')?>";
388
- i18n.coauthors.search_box_text = "<?php _e('Search for an author', 'co-authors-plus')?>";
389
- i18n.coauthors.help_text = "<?php _e('Click on an author to change them. Click on <strong>Delete</strong> to remove them.', 'co-authors-plus')?>";
390
-
391
- <?php if(is_array($coauthors) && !(empty($coauthors))) : ?>
392
- var post_coauthors = [
393
- <?php
394
- foreach($coauthors as $author) {
395
- echo "{";
396
- echo "'login': escape('". $author->user_login ."'),";
397
- echo "'name': escape('". $author->display_name ."'),";
398
- echo "'id': '". $author->ID ."'";
399
- echo "},";
400
- }
401
- ?>
402
- ];
403
- <?php else : ?>
404
- var post_coauthors = [
405
- <?php
406
- echo "{";
407
- echo "'login': '". $current_user->user_login ."',";
408
- echo "'name': '". $current_user->display_name ."',";
409
- echo "'id': '". $current_user->ID ."'";
410
- echo "},";
411
- ?>
412
- ];
413
- <?php endif; ?>
414
- </script>
415
- <?php
416
- }
417
- } // END: js_vars()
418
-
419
- /* Helper to only add javascript to necessary pages
420
- * Avoid bloat on admin
421
- */
422
- private function is_valid_page() {
423
- global $pagenow;
424
-
425
- $pages = array('edit.php', 'post.php', 'post-new.php', 'page.php', 'page-new.php');
426
-
427
- if(in_array($pagenow, $pages)) return true;
428
-
429
- return false;
430
- }
431
-
432
- /* Allows coauthors to edit the post they're coauthors of
433
- *
434
- */
435
- function add_coauthor_cap( $allcaps, $caps, $args ) {
436
-
437
- if(in_array('edit_post', $args) || in_array('edit_others_posts', $args)) {
438
- // @TODO: Fix this disgusting hardcodedness. Ew.
439
- $user_id = $args[1];
440
- $post_id = $args[2];
441
- if(is_coauthor_for_post($user_id, $post_id)) {
442
- // @TODO check to see if can edit publish posts if post is published
443
- // @TODO check to see if can edit posts at all
444
- foreach($caps as $cap) {
445
- $allcaps[$cap] = 1;
446
- }
447
- }
448
- }
449
- return $allcaps;
450
- }
451
-
452
- /* Emails all coauthors when comment added instead of the main author
453
- *
454
- */
455
- function notify_coauthors( $message_headers, $comment_id ) {
456
- //echo '<p>Pre:';
457
- //print_r($message_headers);
458
- $comment = get_comment($comment_id);
459
- $post = get_post($comment->comment_post_ID);
460
- $coauthors = get_coauthors($comment->comment_post_ID);
461
-
462
- $message_headers .= 'cc: ';
463
- $count = 0;
464
- foreach($coauthors as $author) {
465
- $count++;
466
- if($author->ID != $post->post_author){
467
- $message_headers .= $author->user_email;
468
- if($count < count($coauthors)) $message_headers .= ',';
469
- }
470
- }
471
- $message_headers .= "\n";
472
- return $message_headers;
473
- //echo '<p>Post:';
474
- //print_r($message_headers);
475
-
476
- }
477
-
478
- /**
479
- * Loads options for the plugin.
480
- * If option doesn't exist in database, it is added
481
- *
482
- * Note: default values are stored in the $this->options array
483
- * Note: a prefix unique to the plugin is appended to all options. Prefix is stored in $this->options_group
484
- */
485
- protected function load_options ( ) {
486
-
487
- $new_options = array();
488
-
489
- foreach($this->options as $option => $value) {
490
- $name = $this->get_plugin_option_fullname($option);
491
- $return = get_option($name);
492
- if($return === false) {
493
- add_option($name, $value);
494
- $new_array[$option] = $value;
495
- } else {
496
- $new_array[$option] = $return;
497
- }
498
- }
499
- $this->options = $new_array;
500
-
501
- } // END: load_options
502
-
503
-
504
- /**
505
- * Returns option for the plugin specified by $name, e.g. custom_stati_enabled
506
- *
507
- * Note: The plugin option prefix does not need to be included in $name
508
- *
509
- * @param string name of the option
510
- * @return option|null if not found
511
- *
512
- */
513
- function get_plugin_option ( $name ) {
514
- if(is_array($this->options) && $option = $this->options[$name])
515
- return $option;
516
- else
517
- return null;
518
- } // END: get_option
519
-
520
- // Utility function: appends the option prefix and returns the full name of the option as it is stored in the wp_options db
521
- protected function get_plugin_option_fullname ( $name ) {
522
- return $this->options_group . $name;
523
- }
524
-
525
- /* Adds Settings page for Edit Flow
526
- *
527
- */
528
- function settings_page( ) {
529
- global $wp_roles;
530
-
531
- ?>
532
- <div class="wrap">
533
- <div class="icon32" id="icon-options-general"><br/></div>
534
- <h2><?php _e('Co-Authors Plus', 'co-authors-plus') ?></h2>
535
-
536
- <form method="post" action="options.php">
537
- <?php settings_fields($this->options_group); ?>
538
-
539
- <table class="form-table">
540
- <tr valign="top">
541
- <th scope="row"><strong><?php _e('Roles', 'co-authors-plus') ?></strong></th>
542
- <td>
543
- <p>
544
- <label for="allow_subscribers_as_authors">
545
- <input type="checkbox" name="<?php echo $this->get_plugin_option_fullname('allow_subscribers_as_authors') ?>" value="1" <?php echo ($this->get_plugin_option('allow_subscribers_as_authors')) ? 'checked="checked"' : ''; ?> id="allow_subscribers_as_authors" /> <?php _e('Allow subscribers as authors', 'co-authors-plus') ?>
546
- </label> <br />
547
- <span class="description"><?php _e('Enabling this option will allow you to add users with the subscriber role as authors for posts.', 'co-authors-plus') ?></span>
548
- </p>
549
- </td>
550
- </tr>
551
-
552
- </table>
553
-
554
- <p class="submit">
555
- <input type="submit" class="button-primary" value="<?php _e('Save Changes', 'co-authors-plus') ?>" />
556
- </p>
557
- </form>
558
- </div>
559
- <?php
560
- }
561
-
562
-
563
- /* Function updates coauthors from old meta-based storage to taxonomy-based
564
- *
565
- */
566
- function update() {
567
- // Get all posts with meta_key _coauthor
568
- $all_posts = get_posts(array('numberposts' => '-1'));
569
-
570
- //echo '<p>returned posts ('.count($all_posts).'):</p>';
571
- //print_r($posts);
572
- //echo '<hr />';
573
-
574
- if(is_array($all_posts)) {
575
- foreach($all_posts as $single_post) {
576
-
577
- // reset execution time limit
578
- set_time_limit( 60 );
579
-
580
- // create new array
581
- $coauthors = array();
582
- // get author id -- try to use get_profile
583
- $coauthors[] = get_profile_by_id('user_login', (int)$single_post->post_author);
584
- // get coauthors id
585
- $legacy_coauthors = get_post_meta($single_post->ID, '_coauthor');
586
- //print_r($legacy_coauthors);
587
- //echo '<hr />';
588
- if(is_array($legacy_coauthors)) {
589
- //echo '<p>Has Legacy coauthors';
590
- foreach($legacy_coauthors as $legacy_coauthor) {
591
- $legacy_coauthor_login = get_profile_by_id('user_login', (int)$legacy_coauthor);
592
- if($legacy_coauthor_login) $coauthors[] = $legacy_coauthor_login;
593
- }
594
- } else {
595
- //echo '<p>No Legacy coauthors';
596
- }
597
- //echo '<p>Post '.$single_post->ID;
598
- //print_r($coauthors);
599
- //echo '<hr />';
600
- $this->add_coauthors($single_post->ID, $coauthors);
601
-
602
- }
603
- }
604
- }
605
-
606
- /*
607
- * @TODO
608
- * - Add new author
609
- * - Add search-as-you-type to QuikcEdit
610
- * - get_coauthor_meta function
611
- */
612
-
613
- }
614
-
615
- /** Helper Functions **/
616
-
617
- /* Replacement for the default WordPress get_profile function, since that doesn't allow for search by user_id
618
- * Returns a the specified column value for the specified user
619
- */
620
-
621
- if(!function_exists('get_profile_by_id')) {
622
- function get_profile_by_id($field, $user_id) {
623
- global $wpdb;
624
- if($field && $user_id) return $wpdb->get_var( $wpdb->prepare("SELECT $field FROM $wpdb->users WHERE ID = %d", $user_id) );
625
- return false;
626
- }
627
- }
628
-
629
- /** Let's get the plugin rolling **/
630
-
631
- // Create new instance of the edit_flow object
632
- global $coauthors_plus;
633
- $coauthors_plus = new coauthors_plus();
634
- //$coauthors_plus->update();
635
-
636
- // Core hooks to initialize the plugin
637
- add_action('init', array(&$coauthors_plus,'init'));
638
- add_action('admin_init', array(&$coauthors_plus,'admin_init'));
639
-
640
- // Hook to perform action when plugin activated
641
- register_activation_hook( __FILE__, array(&$edit_flow, 'activate_plugin'));
642
-
643
- // Upgrade to new taxonomy system
644
- if(floatval(get_option('coauthors_plus_version')) < 2.0) $coauthors_plus->update();
645
-
646
- update_option('coauthors_plus_version', COAUTHORS_PLUS_VERSION);
647
-
648
- ?>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
css/co-authors-plus.css ADDED
@@ -0,0 +1,91 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #coauthors-list {
2
+ width: 100%;
3
+ padding: 0 5px;
4
+ }
5
+ #coauthors-list td {
6
+ padding-left:0;
7
+ padding-right:0;
8
+ vertical-align: middle;
9
+ background: #F9F9F9;
10
+ }
11
+
12
+ #coauthors-list .coauthor-row {
13
+ overflow: hidden;
14
+ }
15
+ #coauthors-list .coauthor-tag {
16
+ width: 200px;
17
+ display: block;
18
+ padding: 10px 3px 5px;
19
+ font-size: 13px;
20
+ color: #21759B;
21
+ border-bottom: 1px solid #21759B;
22
+ cursor: text;
23
+ float: left;
24
+ }
25
+ #coauthors-list .coauthor-tag:hover {
26
+ cursor: text;
27
+ background: #EAF2FA;
28
+ color:#D54E21;
29
+ }
30
+ #coauthors-list .coauthor-gravatar {
31
+ float: right;
32
+ height: 25px;
33
+ margin-right: 5px;
34
+ margin-top: -5px;
35
+ width: 25px;
36
+ }
37
+ #coauthors-list .coauthor-suggest {
38
+ margin: 5px 0;
39
+ color: #888;
40
+ width: 200px;
41
+ float: left;
42
+ }
43
+ #coauthors-list .coauthor-suggest:focus {
44
+ color: #333;
45
+ }
46
+ #coauthors-list .coauthors-author-options {
47
+ overflow: hidden;
48
+ padding: 7px 0 0 7px;
49
+ }
50
+ #coauthors-list .delete-coauthor {
51
+ display: block;
52
+ float: left;
53
+ padding: 5px;
54
+ text-decoration: underline;
55
+ color: #FF0000;
56
+ font-size: 11px;
57
+
58
+ border: 1px solid transparent;
59
+ }
60
+ #coauthors-list .delete-coauthor:hover {
61
+ cursor: pointer;
62
+ background: #FF0000;
63
+ color: #fff;
64
+
65
+ border: 1px solid #FF0000;
66
+ border-radius: 3px;
67
+ -moz-border-radius: 3px;
68
+ -webkit-border-radius: 3px;
69
+ }
70
+ #coauthors-loading {
71
+ margin: 10px 0px 5px 10px;
72
+ }
73
+
74
+ #coauthors-readonly {
75
+ }
76
+ #coauthors-readonly ul {
77
+ list-style: none;
78
+ padding: 0;
79
+ margin: 0;
80
+ }
81
+ #coauthors-readonly ul li {
82
+ }
83
+ #coauthors-readonly ul li .avatar {
84
+ float: left;
85
+ }
86
+ #coauthors-readonly ul li .coauthor-tag {
87
+ display:block;
88
+ padding: 5px 3px;
89
+ margin-left: 30px;
90
+ font-size: 13px;
91
+ }
css/jquery.aceditable.css ADDED
@@ -0,0 +1,80 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .ac_results {
2
+ padding: 0px;
3
+ border: 0px solid black;
4
+ background-color: white;
5
+ overflow: hidden;
6
+ z-index: 99999;
7
+ }
8
+
9
+ .ac_results ul {
10
+ width: 100%;
11
+ list-style-position: outside;
12
+ list-style: none;
13
+ padding: 0;
14
+ margin: 0px;
15
+ -moz-border-radius-bottomleft:5px;
16
+ -moz-border-radius-bottomright:5px;
17
+ -moz-border-radius-topleft:0;
18
+ -moz-border-radius-topright:0;
19
+ -webkit-border-radius-bottomleft:5px;
20
+ -webkit-border-radius-bottomright:5px;
21
+ background-color:#FFFFFF;
22
+ border-color:#F3F3F3 #CCCCCC #CCCCCC;
23
+ border-style:solid;
24
+ border-width:1px;
25
+ }
26
+ .ac_results li.noresult_msg {
27
+ background:#F2F2F2 none repeat scroll 0 0;
28
+ border:2px solid #DDDDDD;
29
+ font-size:93%;
30
+ padding:5px 6px;
31
+ }
32
+ .ac_results li.start_msg {
33
+ background:#F2F2F2 none repeat scroll 0 0;
34
+ border:2px solid #DDDDDD;
35
+ font-size:93%;
36
+ padding:5px 6px;
37
+ }
38
+ .ac_results li.end_msg {
39
+ background:#F2F2F2 none repeat scroll 0 0;
40
+ border:2px solid #DDDDDD;
41
+ font-size:93%;
42
+ padding:5px 6px;
43
+ }
44
+
45
+ .ac_results li {
46
+ margin: 0px;
47
+ text-align:left;
48
+ padding: 3px 6px;
49
+ cursor: default;
50
+ display: block;
51
+ border-top:1px solid #EEEEEE;
52
+ /*
53
+ if width will be 100% horizontal scrollbar will apear
54
+ when scroll mode will be used
55
+ */
56
+ /*width: 100%;*/
57
+ font: menu;
58
+ font-size: 12px;
59
+ /*
60
+ it is very important, if line-height not setted or setted
61
+ in relative units scroll will be broken in firefox
62
+ */
63
+ line-height: 16px;
64
+ overflow: hidden;
65
+ }
66
+
67
+ .ac_loading {
68
+ background: white url('indicator.gif') right center no-repeat;
69
+ }
70
+
71
+ .ac_odd {
72
+ background-color:;
73
+ }
74
+ .ac_even {
75
+ background-color:;
76
+ }
77
+
78
+ .ac_over {
79
+ background-color: #dddddd;
80
+ }
js/co-authors-plus.js ADDED
@@ -0,0 +1,439 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ jQuery(document).ready(function () {
2
+
3
+ /*
4
+ * Click handler for the delete button
5
+ * @param event
6
+ */
7
+ var coauthors_delete_onclick = function(e){
8
+ if(confirm(coAuthorsPlusStrings.confirm_delete)) {
9
+ return coauthors_delete(this);
10
+ }
11
+ return false;
12
+ };
13
+
14
+ function coauthors_delete( elem ) {
15
+
16
+ var $coauthor_row = jQuery(elem).closest('.coauthor-row');
17
+ $coauthor_row.remove();
18
+
19
+ return true;
20
+ }
21
+
22
+ var coauthors_edit_onclick = function(event) {
23
+ var $tag = jQuery(this);
24
+
25
+ var $co = $tag.prev();
26
+
27
+ $tag.hide();
28
+ $co.show()
29
+ .focus()
30
+ ;
31
+
32
+ $co.previousAuthor = $tag.text();
33
+ }
34
+
35
+ /*
36
+ * Save coauthor
37
+ * @param int Author ID
38
+ * @param string Author Name
39
+ * @param object The autosuggest input box
40
+ */
41
+ function coauthors_save_coauthor(author, co) {
42
+
43
+ // get sibling <span> and update
44
+ co.siblings('.coauthor-tag')
45
+ .html(author.name)
46
+ .append(coauthors_create_author_gravatar(author))
47
+ .show()
48
+ ;
49
+
50
+ // Update the value of the hidden input
51
+ co.siblings('input[name="coauthors[]"]').val(author.login);
52
+ }
53
+
54
+
55
+ /*
56
+ * Add coauthor
57
+ * @param string Author Name
58
+ * @param object The autosuggest input box
59
+ * @param boolean Initial set up or not?
60
+ */
61
+ // function coauthors_add_coauthor(authorID, authorName, co, init, count){
62
+ function coauthors_add_coauthor(author, co, init, count){
63
+
64
+ // Check if editing
65
+ if( co && co.siblings('.coauthor-tag').length ) {
66
+ coauthors_save_coauthor(author, co);
67
+ } else {
68
+ // Not editing, so we create a new author entry
69
+ if(count == 0) {
70
+ var coName = (count == 0) ? 'coauthors-main' : '';
71
+ // Add new author to <select>
72
+ //coauthors_select_author( author );
73
+ var options = {};
74
+ } else {
75
+ var options = { addDelete: true, addEdit: false };
76
+ }
77
+
78
+ // Create autosuggest box and text tag
79
+ if(!co) var co = coauthors_create_autosuggest(author, coName)
80
+ var tag = coauthors_create_author_tag(author);
81
+ var input = coauthors_create_author_hidden_input(author);
82
+ var $gravatar = coauthors_create_author_gravatar(author, 25);
83
+
84
+ tag.append($gravatar);
85
+
86
+ coauthors_add_to_table(co, tag, input, options);
87
+
88
+ if(!init) {
89
+ // Create new author-suggest and append it to a new row
90
+ var newCO = coauthors_create_autosuggest('', false);
91
+ coauthors_add_to_table(newCO);
92
+ move_loading(newCO);
93
+ }
94
+ }
95
+
96
+ co.bind('blur', coauthors_stop_editing);
97
+
98
+ // Set the value for the auto-suggest box to the Author's name and hide it
99
+ co.val(unescape(author.name))
100
+ .hide()
101
+ .unbind('focus')
102
+ ;
103
+
104
+ return true;
105
+ }
106
+
107
+
108
+ /*
109
+ * Add the autosuggest box and text tag to the Co-Authors table
110
+ * @param object Autosuggest input box
111
+ * @param object Text tag
112
+ * @param
113
+ */
114
+ function coauthors_add_to_table( co, tag, input, options ) {
115
+ if(co) {
116
+ var $div = jQuery('<div/>')
117
+ .addClass('suggest')
118
+ .addClass('coauthor-row')
119
+ .append(co)
120
+ .append(tag)
121
+ .append(input)
122
+ ;
123
+
124
+ //Add buttons to row
125
+ if(tag) coauthors_insert_author_edit_cells($div, options);
126
+
127
+ jQuery('#coauthors-list').append($div);
128
+ }
129
+ }
130
+
131
+ /*
132
+ * Adds a delete and edit button next to an author
133
+ * @param object The row to which the new author should be added
134
+ */
135
+ function coauthors_insert_author_edit_cells($div, options){
136
+
137
+ var $options = jQuery('<div/>')
138
+ .addClass('coauthors-author-options')
139
+ ;
140
+
141
+ /*
142
+ if(options.addEdit) {
143
+ var editBtn = jQuery('<span></span>')
144
+ .addClass('edit-coauthor')
145
+ .text(coAuthorsPlusStrings.edit_label)
146
+ .bind('click', coauthors_edit_onclick)
147
+ ;
148
+ td.append(editBtn);
149
+ }
150
+ */
151
+ if(options.addDelete) {
152
+ var deleteBtn = jQuery('<span/>')
153
+ .addClass('delete-coauthor')
154
+ .text(coAuthorsPlusStrings.delete_label)
155
+ .bind('click', coauthors_delete_onclick)
156
+ ;
157
+ $options.append(deleteBtn);
158
+ }
159
+
160
+ $div.append($options);
161
+ return $div;
162
+ }
163
+
164
+ /*
165
+ * Creates autosuggest input box
166
+ * @param string [optional] Name of the author
167
+ * @param string [optional] Name to be applied to the input box
168
+ */
169
+ function coauthors_create_autosuggest(authorName, inputName) {
170
+
171
+ if(!inputName) inputName = 'coauthorsinput[]';
172
+
173
+ var $co = jQuery('<input/>');
174
+
175
+ $co.attr({
176
+ 'class': 'coauthor-suggest'
177
+ , 'name': inputName
178
+ })
179
+ .appendTo($coauthors_div)
180
+ /*
181
+ .autocomplete(coauthors_all, {
182
+ matchContains: true
183
+ , scroll: false
184
+ , formatItem: function(row) { return row[2] + ' ' + row[0] + ' | ' + row[1] }
185
+ , formatResult: function(row) { return row[1]; }
186
+ })
187
+ */
188
+ .suggest(coAuthorsPlus_ajax_suggest_link, {
189
+ onSelect: coauthors_autosuggest_select
190
+ })
191
+ .keydown(coauthors_autosuggest_keydown)
192
+ ;
193
+
194
+ if(authorName)
195
+ $co.attr( 'value', unescape( authorName ) );
196
+ else
197
+ $co.attr( 'value', coAuthorsPlusStrings.search_box_text )
198
+ .focus( function(){ $co.val( '' ) } )
199
+ .blur( function(){ $co.val( coAuthorsPlusStrings.search_box_text ) } )
200
+ ;
201
+
202
+ return $co;
203
+
204
+ }
205
+
206
+ // Callback for when a user selects an author
207
+ function coauthors_autosuggest_select() {
208
+ $this = jQuery(this);
209
+ var vals = this.value.split("|");
210
+
211
+ var author = {}
212
+ author.id = jQuery.trim(vals[0]);
213
+ author.login = jQuery.trim(vals[1]);
214
+ author.name = jQuery.trim(vals[2]);
215
+ author.email = jQuery.trim(vals[3]);
216
+
217
+ if(author.id=="New") {
218
+ //alert('Eventually, this will allow you to add a new author right from here. But it\'s not ready yet. *sigh*');
219
+ coauthors_new_author_display(name);
220
+ } else {
221
+ //coauthors_add_coauthor(login, name, co);
222
+ coauthors_add_coauthor(author, $this);
223
+ }
224
+ }
225
+
226
+ // Prevent the enter key from triggering a submit
227
+ function coauthors_autosuggest_keydown(e) {
228
+ if(e.keyCode == 13) {return false;}
229
+ }
230
+
231
+ /*
232
+ * Blur handler for autosuggest input box
233
+ * @param event
234
+ */
235
+ function coauthors_stop_editing(event) {
236
+
237
+ var co = jQuery(this);
238
+ var tag = jQuery(co.next());
239
+
240
+ co.attr('value',tag.text());
241
+
242
+ co.hide();
243
+ tag.show();
244
+
245
+ // editing = false;
246
+ }
247
+
248
+ /*
249
+ * Creates the text tag for an author
250
+ * @param string Name of the author
251
+ */
252
+ function coauthors_create_author_tag(author) {
253
+
254
+ var $tag = jQuery('<span></span>')
255
+ .html(unescape(author.name))
256
+ .attr('title', coAuthorsPlusStrings.input_box_title)
257
+ .addClass('coauthor-tag')
258
+ // Add Click event to edit
259
+ .click(coauthors_edit_onclick);
260
+ return $tag;
261
+ }
262
+
263
+ function coauthors_create_author_gravatar(author, size) {
264
+
265
+ var gravatar_link = get_gravatar_link(author.email, size);
266
+
267
+ var $gravatar = jQuery('<img/>')
268
+ .attr('alt', author.name)
269
+ .attr('src', gravatar_link)
270
+ .addClass('coauthor-gravatar')
271
+ ;
272
+ return $gravatar;
273
+ }
274
+
275
+ // MD5 (Message-Digest Algorithm) by WebToolkit -- needed for gravatars
276
+ // http://www.webtoolkit.info/javascript-md5.html
277
+ function MD5(s){function L(k,d){return(k<<d)|(k>>>(32-d))}function K(G,k){var I,d,F,H,x;F=(G&2147483648);H=(k&2147483648);I=(G&1073741824);d=(k&1073741824);x=(G&1073741823)+(k&1073741823);if(I&d){return(x^2147483648^F^H)}if(I|d){if(x&1073741824){return(x^3221225472^F^H)}else{return(x^1073741824^F^H)}}else{return(x^F^H)}}function r(d,F,k){return(d&F)|((~d)&k)}function q(d,F,k){return(d&k)|(F&(~k))}function p(d,F,k){return(d^F^k)}function n(d,F,k){return(F^(d|(~k)))}function u(G,F,aa,Z,k,H,I){G=K(G,K(K(r(F,aa,Z),k),I));return K(L(G,H),F)}function f(G,F,aa,Z,k,H,I){G=K(G,K(K(q(F,aa,Z),k),I));return K(L(G,H),F)}function D(G,F,aa,Z,k,H,I){G=K(G,K(K(p(F,aa,Z),k),I));return K(L(G,H),F)}function t(G,F,aa,Z,k,H,I){G=K(G,K(K(n(F,aa,Z),k),I));return K(L(G,H),F)}function e(G){var Z;var F=G.length;var x=F+8;var k=(x-(x%64))/64;var I=(k+1)*16;var aa=Array(I-1);var d=0;var H=0;while(H<F){Z=(H-(H%4))/4;d=(H%4)*8;aa[Z]=(aa[Z]|(G.charCodeAt(H)<<d));H++}Z=(H-(H%4))/4;d=(H%4)*8;aa[Z]=aa[Z]|(128<<d);aa[I-2]=F<<3;aa[I-1]=F>>>29;return aa}function B(x){var k="",F="",G,d;for(d=0;d<=3;d++){G=(x>>>(d*8))&255;F="0"+G.toString(16);k=k+F.substr(F.length-2,2)}return k}function J(k){k=k.replace(/\r\n/g,"\n");var d="";for(var F=0;F<k.length;F++){var x=k.charCodeAt(F);if(x<128){d+=String.fromCharCode(x)}else{if((x>127)&&(x<2048)){d+=String.fromCharCode((x>>6)|192);d+=String.fromCharCode((x&63)|128)}else{d+=String.fromCharCode((x>>12)|224);d+=String.fromCharCode(((x>>6)&63)|128);d+=String.fromCharCode((x&63)|128)}}}return d}var C=Array();var P,h,E,v,g,Y,X,W,V;var S=7,Q=12,N=17,M=22;var A=5,z=9,y=14,w=20;var o=4,m=11,l=16,j=23;var U=6,T=10,R=15,O=21;s=J(s);C=e(s);Y=1732584193;X=4023233417;W=2562383102;V=271733878;for(P=0;P<C.length;P+=16){h=Y;E=X;v=W;g=V;Y=u(Y,X,W,V,C[P+0],S,3614090360);V=u(V,Y,X,W,C[P+1],Q,3905402710);W=u(W,V,Y,X,C[P+2],N,606105819);X=u(X,W,V,Y,C[P+3],M,3250441966);Y=u(Y,X,W,V,C[P+4],S,4118548399);V=u(V,Y,X,W,C[P+5],Q,1200080426);W=u(W,V,Y,X,C[P+6],N,2821735955);X=u(X,W,V,Y,C[P+7],M,4249261313);Y=u(Y,X,W,V,C[P+8],S,1770035416);V=u(V,Y,X,W,C[P+9],Q,2336552879);W=u(W,V,Y,X,C[P+10],N,4294925233);X=u(X,W,V,Y,C[P+11],M,2304563134);Y=u(Y,X,W,V,C[P+12],S,1804603682);V=u(V,Y,X,W,C[P+13],Q,4254626195);W=u(W,V,Y,X,C[P+14],N,2792965006);X=u(X,W,V,Y,C[P+15],M,1236535329);Y=f(Y,X,W,V,C[P+1],A,4129170786);V=f(V,Y,X,W,C[P+6],z,3225465664);W=f(W,V,Y,X,C[P+11],y,643717713);X=f(X,W,V,Y,C[P+0],w,3921069994);Y=f(Y,X,W,V,C[P+5],A,3593408605);V=f(V,Y,X,W,C[P+10],z,38016083);W=f(W,V,Y,X,C[P+15],y,3634488961);X=f(X,W,V,Y,C[P+4],w,3889429448);Y=f(Y,X,W,V,C[P+9],A,568446438);V=f(V,Y,X,W,C[P+14],z,3275163606);W=f(W,V,Y,X,C[P+3],y,4107603335);X=f(X,W,V,Y,C[P+8],w,1163531501);Y=f(Y,X,W,V,C[P+13],A,2850285829);V=f(V,Y,X,W,C[P+2],z,4243563512);W=f(W,V,Y,X,C[P+7],y,1735328473);X=f(X,W,V,Y,C[P+12],w,2368359562);Y=D(Y,X,W,V,C[P+5],o,4294588738);V=D(V,Y,X,W,C[P+8],m,2272392833);W=D(W,V,Y,X,C[P+11],l,1839030562);X=D(X,W,V,Y,C[P+14],j,4259657740);Y=D(Y,X,W,V,C[P+1],o,2763975236);V=D(V,Y,X,W,C[P+4],m,1272893353);W=D(W,V,Y,X,C[P+7],l,4139469664);X=D(X,W,V,Y,C[P+10],j,3200236656);Y=D(Y,X,W,V,C[P+13],o,681279174);V=D(V,Y,X,W,C[P+0],m,3936430074);W=D(W,V,Y,X,C[P+3],l,3572445317);X=D(X,W,V,Y,C[P+6],j,76029189);Y=D(Y,X,W,V,C[P+9],o,3654602809);V=D(V,Y,X,W,C[P+12],m,3873151461);W=D(W,V,Y,X,C[P+15],l,530742520);X=D(X,W,V,Y,C[P+2],j,3299628645);Y=t(Y,X,W,V,C[P+0],U,4096336452);V=t(V,Y,X,W,C[P+7],T,1126891415);W=t(W,V,Y,X,C[P+14],R,2878612391);X=t(X,W,V,Y,C[P+5],O,4237533241);Y=t(Y,X,W,V,C[P+12],U,1700485571);V=t(V,Y,X,W,C[P+3],T,2399980690);W=t(W,V,Y,X,C[P+10],R,4293915773);X=t(X,W,V,Y,C[P+1],O,2240044497);Y=t(Y,X,W,V,C[P+8],U,1873313359);V=t(V,Y,X,W,C[P+15],T,4264355552);W=t(W,V,Y,X,C[P+6],R,2734768916);X=t(X,W,V,Y,C[P+13],O,1309151649);Y=t(Y,X,W,V,C[P+4],U,4149444226);V=t(V,Y,X,W,C[P+11],T,3174756917);W=t(W,V,Y,X,C[P+2],R,718787259);X=t(X,W,V,Y,C[P+9],O,3951481745);Y=K(Y,h);X=K(X,E);W=K(W,v);V=K(V,g)}var i=B(Y)+B(X)+B(W)+B(V);return i.toLowerCase()};
278
+
279
+ // Adapted from http://www.deluxeblogtips.com/2010/04/get-gravatar-using-only-javascript.html
280
+ function get_gravatar_link(email, size) {
281
+ var size = size || 80;
282
+ return 'http://www.gravatar.com/avatar/' + MD5(email) + '.jpg?s=' + size;
283
+ }
284
+
285
+ /*
286
+ * Creates the text tag for an author
287
+ * @param string Name of the author
288
+ */
289
+ function coauthors_create_author_hidden_input (author) {
290
+ var input = jQuery('<input />')
291
+ .attr({
292
+ 'type': 'hidden',
293
+ 'id': 'coauthors_hidden_input',
294
+ 'name': 'coauthors[]',
295
+ 'value': unescape(author.login)
296
+ })
297
+ ;
298
+
299
+ return input;
300
+ }
301
+
302
+ /*
303
+ * Creates display for adding new author
304
+ * @param string Name of the author
305
+ */
306
+ /*
307
+ function coauthors_new_author_create_display ( ) {
308
+
309
+ var author_window = jQuery('<div></div>')
310
+ .appendTo(jQuery('body'))
311
+ .attr('id','new-author-window')
312
+ .addClass('wrap')
313
+ .append(
314
+ jQuery('<div></div>')
315
+ .addClass('icon32')
316
+ .attr('id','icon-users')
317
+ )
318
+ .append(
319
+ jQuery('<h2></h2>')
320
+ .text('Add new author')
321
+ .attr('id', 'add-new-user')
322
+
323
+ )
324
+ .append(
325
+ jQuery('<div/>')
326
+ .attr('id', 'createauthor-ajax-response')
327
+ )
328
+ ;
329
+
330
+ var author_form = jQuery('<form />')
331
+ .appendTo(author_window)
332
+ .attr({
333
+ id: 'createauthor',
334
+ name: 'createauthor',
335
+ method: 'post',
336
+ action: ''
337
+ })
338
+ ;
339
+
340
+
341
+
342
+ var create_text_field = function( name, id, label) {
343
+
344
+ var field = jQuery('<input />')
345
+ .attr({
346
+ type:'text',
347
+ name: name,
348
+ id: id,
349
+ })
350
+ var label = jQuery('<label></label>')
351
+ .attr('for',name)
352
+ .text(label)
353
+
354
+ //return {field, label};
355
+
356
+ };
357
+
358
+ create_field('user_login', 'user_login', 'User Name');
359
+ create_field('first_name', 'first_name', 'First Name');
360
+
361
+ //last_name
362
+ //email
363
+ //pass1
364
+ //email password checkbox
365
+ //role
366
+ }
367
+ */
368
+
369
+ // Add the controls to add co-authors
370
+ var $coauthors_div = jQuery('#coauthors-edit');
371
+
372
+ if( $coauthors_div.length ) {
373
+ // Create the co-authors table
374
+ var table = jQuery('<div/>')
375
+ .attr('id', 'coauthors-list')
376
+ ;
377
+ $coauthors_div.append(table);
378
+ }
379
+
380
+ var $post_coauthor_logins = jQuery('input[name="coauthors[]"]');
381
+ var $post_coauthor_names = jQuery('input[name="coauthorsinput[]"]');
382
+ var $post_coauthor_emails = jQuery('input[name="coauthorsemails[]"]');
383
+
384
+ post_coauthors = [];
385
+
386
+ for(var i = 0; i < $post_coauthor_logins.length; i++) {
387
+ post_coauthors.push({
388
+ login: $post_coauthor_logins[i].value,
389
+ name: $post_coauthor_names[i].value,
390
+ email: $post_coauthor_emails[i].value,
391
+ });
392
+ }
393
+
394
+ // Select authors already added to the post
395
+ var addedAlready = [];
396
+ //jQuery('#the-list tr').each(function(){
397
+ var count = 0;
398
+ jQuery.each(post_coauthors, function() {
399
+ coauthors_add_coauthor(this, undefined, true, count );
400
+ count++;
401
+ });
402
+
403
+ // Create new author-suggest and append it to a new row
404
+ var newCO = coauthors_create_autosuggest('', false);
405
+ coauthors_add_to_table(newCO);
406
+
407
+ $coauthors_loading = jQuery('#ajax-loading').clone().attr('id', 'coauthors-loading');
408
+ move_loading(newCO);
409
+
410
+ // Remove the read-only coauthors so we don't get craziness
411
+ jQuery('#coauthors-readonly').remove();
412
+
413
+ function show_loading() {
414
+ $coauthors_loading.css('visibility', 'visible');
415
+ }
416
+ function hide_loading() {
417
+ $coauthors_loading.css('visibility', 'hidden');
418
+ }
419
+ function move_loading($input) {
420
+ $coauthors_loading.insertAfter($input);
421
+ }
422
+ // Show laoding cursor for autocomplete ajax requests
423
+ jQuery(document).ajaxSend(function(e, xhr, settings) {
424
+ if( settings.url.indexOf(coAuthorsPlus_ajax_suggest_link) != -1 ) {
425
+ show_loading();
426
+ }
427
+ });
428
+ // Hide laoding cursor when autocomplete ajax requests are finished
429
+ jQuery(document).ajaxComplete(function(e, xhr, settings) {
430
+ if( settings.url.indexOf(coAuthorsPlus_ajax_suggest_link) != -1 )
431
+ hide_loading();
432
+ });
433
+
434
+ });
435
+
436
+ if( typeof(console) === 'undefined' ) {
437
+ var console = {}
438
+ console.log = console.error = function() {};
439
+ }
js/jquery.aceditable.dev.js ADDED
@@ -0,0 +1,1176 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /*
2
+ * jQuery ContentEditable AC Autocompletion Plugin
3
+ *
4
+ * A signifigant fork of the Original AutoComplete by J�rn Zaeffererrequest
5
+ *
6
+ * Copyright (c) 2009 J�rn Zaeffererrequest and Aaron Raddon
7
+ *
8
+ * Dual licensed under the MIT and GPL licenses:
9
+ * http://www.opensource.org/licenses/mit-license.php
10
+ * http://www.gnu.org/licenses/gpl.html
11
+ *
12
+ * Revision: $Id: jquery.autocomplete.js 15 2009-08-22 10:30:27Z joern.zaefferer $
13
+ */
14
+
15
+ (function($) {
16
+
17
+ if (window['log'] == undefined){
18
+ window['log'] = {
19
+ toggle: function() {},
20
+ move: function() {},
21
+ resize: function() {},
22
+ clear: function() {},
23
+ debug: function() {},
24
+ info: function() {},
25
+ warn: function() {},
26
+ error: function() {},
27
+ profile: function() {}
28
+ };
29
+ }
30
+
31
+ $.fn.extend({
32
+ autocomplete: function(urlOrData, options) {
33
+ var isUrl = typeof urlOrData == "string";
34
+ options = $.extend({
35
+ formatEditableResult: function(row) { return '<a contenteditable="false" href="#" tabindex="-1" >@' + row[options.jsonterm] + '</a>&nbsp;';},
36
+ formatResult: function(row) { return row[options.jsonterm];},
37
+ formatItem: function(row) { return row[options.jsonterm]; }
38
+ }, $.Autocompleter.defaults, {
39
+ url: isUrl ? urlOrData : null,
40
+ data: isUrl ? null : urlOrData,
41
+ delay: isUrl ? $.Autocompleter.defaults.delay : 10,
42
+ max: options && !options.scroll ? 10 : 150
43
+ }, options);
44
+
45
+ // if highlight is set to false, replace it with a do-nothing function
46
+ options.highlight = options.highlight || function(value) { return value; };
47
+
48
+ // if the formatMatch option is not specified, then use formatItem for backwards compatibility
49
+ options.formatMatch = options.formatMatch || options.formatItem;
50
+
51
+ return this.each(function() {
52
+ new $.Autocompleter(this, options);
53
+ });
54
+ },
55
+ result: function(handler) {
56
+ return this.bind("result", handler);
57
+ },
58
+ search: function(handler) {
59
+ return this.trigger("search", [handler]);
60
+ },
61
+ flushCache: function() {
62
+ return this.trigger("flushCache");
63
+ },
64
+ setOptions: function(options){
65
+ return this.trigger("setOptions", [options]);
66
+ },
67
+ unautocomplete: function() {
68
+ return this.trigger("unautocomplete");
69
+ },
70
+ notfound: function() {
71
+ return this.trigger("ace_notfound");
72
+ }
73
+ });
74
+
75
+ $.Autocompleter = function(input, options) {
76
+
77
+ var KEY = {
78
+ UP: 38,
79
+ DOWN: 40,
80
+ DEL: 46,
81
+ TAB: 9,
82
+ RETURN: 13,
83
+ SHIFT: 16,
84
+ ESC: 27,
85
+ COMMA: 188,
86
+ PAGEUP: 33,
87
+ PAGEDOWN: 34,
88
+ BACKSPACE: 8,
89
+ SPACE: 32,
90
+ LEFT: 37,
91
+ RIGHT: 39,
92
+ AT: 50,
93
+ POUND:34,
94
+ DOLLAR:52,
95
+ SEMIC:59
96
+ };
97
+ // Create $ object for input element
98
+ var $input = $(input).attr("autocomplete", "off").addClass(options.inputClass);
99
+
100
+ var timeout;
101
+ var previousValue = "";
102
+ var cache = $.Autocompleter.Cache(options);
103
+ var hasFocus = 0;
104
+ var cursorStart = 0;
105
+ var editable = null;
106
+ var editableSelection, editableRange;
107
+ var lastKeyPressCode;
108
+ var preKeyPressCode;
109
+ var prePreKeyPressCode;
110
+ var hotKey = "@";
111
+ var autocActive = options.hotkeymode ? false : true;
112
+ var config = {
113
+ mouseDownOnSelect: false
114
+ };
115
+ var select = $.Autocompleter.Select(options, input, selectCurrent, config);
116
+
117
+ var blockSubmit;
118
+
119
+ if (options.hotkeymode) {
120
+ options.multiple = false;
121
+ }
122
+
123
+ // prevent form submit in opera when selecting with return key
124
+ $.browser.opera && $(input.form).bind("submit.autocomplete", function() {
125
+ if (blockSubmit) {
126
+ blockSubmit = false;
127
+ return false;
128
+ }
129
+ });
130
+ if (input.value == undefined && options.width < 1){
131
+ editable = $(input)[0];
132
+ options.width = $(input).parent().parent().width();
133
+ options.left = $(input).parent().parent().offset().left;
134
+ }
135
+
136
+ // only opera doesn't trigger keydown multiple times while pressed, others don't work with keypress at all
137
+ $input.bind(($.browser.opera ? "keypress" : "keydown") + ".autocomplete", function(event) {
138
+ // a keypress means the input has focus
139
+ // avoids issue where input had focus before the autocomplete was applied
140
+ hasFocus = 1;
141
+ var k=event.keyCode || event.which; // keyCode == 0 in Gecko/FF on keypress
142
+ //log.debug("keypress: " + k);
143
+ if (k == KEY.RETURN){
144
+ log.debug("in k = " + k + ' options.supressReturn =' + options.supressReturn)
145
+ }
146
+ // track history, probably should push/pop an array
147
+ prePreKeyPressCode = preKeyPressCode;
148
+ preKeyPressCode = lastKeyPressCode;
149
+ lastKeyPressCode = k;
150
+ if (options.hotkeymode && autocActive === false){
151
+ if (k == KEY.AT && event.shiftKey && (KEY.SPACE == prePreKeyPressCode || prePreKeyPressCode == undefined)){
152
+ autocActive = true;
153
+ log.debug("AutoComplete now active in Hotkey mode")
154
+ clearTimeout(timeout);
155
+ timeout = setTimeout(onChange, options.delay);
156
+ }
157
+ return;
158
+ }
159
+ switch(k) {
160
+
161
+ case KEY.UP:
162
+ event.preventDefault();
163
+ if ( select.visible() ) {
164
+ select.prev();
165
+ } else {
166
+ onChange(0, true);
167
+ }
168
+ break;
169
+
170
+ case KEY.DOWN:
171
+ event.preventDefault();
172
+ if ( select.visible() ) {
173
+ select.next();
174
+ } else {
175
+ onChange(0, true);
176
+ }
177
+ break;
178
+
179
+ case KEY.PAGEUP:
180
+ event.preventDefault();
181
+ if ( select.visible() ) {
182
+ select.pageUp();
183
+ } else {
184
+ onChange(0, true);
185
+ }
186
+ break;
187
+
188
+ case KEY.PAGEDOWN:
189
+ event.preventDefault();
190
+ if ( select.visible() ) {
191
+ select.pageDown();
192
+ } else {
193
+ onChange(0, true);
194
+ }
195
+ break;
196
+
197
+ // matches also semicolon
198
+ case options.multiple && $.trim(options.multipleSeparator) == "," && KEY.COMMA:
199
+ case KEY.TAB:
200
+ case KEY.RETURN:
201
+ case KEY.RIGHT:
202
+ case KEY.SEMIC:
203
+ if (k == KEY.RETURN)
204
+ event.preventDefault();
205
+ if( selectCurrent() ) {
206
+ // stop default to prevent a form submit, Opera needs special handling
207
+ log.debug("in k = " + k + ' options.supressReturn =' + options.supressReturn)
208
+ if (k == KEY.RETURN && options.supressReturn)
209
+ event.preventDefault();
210
+ blockSubmit = true;
211
+ hideResultsNow();
212
+ if (options.hotkeymode || options.multiple === true){
213
+ log.debug("in return false")
214
+ return false;
215
+ }
216
+ } else {
217
+ log.debug("nada found? trigger notfound?");
218
+ $input.trigger("ace_notfound", {});
219
+ hideResultsNow();
220
+ if (k == KEY.RETURN && options.supressReturn)
221
+ event.preventDefault();
222
+ }
223
+ if (k == KEY.TAB || k == KEY.RETURN){
224
+ log.debug("was tab")
225
+ }
226
+ break;
227
+
228
+ case KEY.ESC:
229
+ select.hide();
230
+ break;
231
+
232
+ default:
233
+ if( autocActive === true ) {
234
+ clearTimeout(timeout);
235
+ timeout = setTimeout(onChange, options.delay);
236
+ }
237
+ break;
238
+ }
239
+ }).focus(function(){
240
+ // track whether the field has focus, we shouldn't process any
241
+ // results if the field no longer has focus
242
+ log.debug("has focus")
243
+ hasFocus++;
244
+ if( autocActive === true && options.hotkeymode) {
245
+ onChange(0, true);
246
+ } else if (!options.hotkeymode) {
247
+ if (!autocActive)
248
+ autocActive = true;
249
+ onChange(0, true);
250
+ }
251
+ }).blur(function() {
252
+ hasFocus = 0;
253
+ if (!config.mouseDownOnSelect) {
254
+ hideResults();
255
+ }
256
+ }).click(function() {
257
+ // show select when clicking in a focused field
258
+ if ( hasFocus++ > 1 && !select.visible() ) {
259
+ onChange(0, true);
260
+ } else {
261
+ log.debug('hasfocus = ' + hasFocus);
262
+ log.debug('hasfocus = ' + (hasFocus > 1));
263
+ log.debug('visible()' + (!select.visible()))
264
+ log.debug('visible()' + select.visible())
265
+ }
266
+ }).bind('result',function(){
267
+ hasFocus = 0;
268
+ hideResults();
269
+ }).bind("search", function() {
270
+ // TODO why not just specifying both arguments?
271
+ var fn = (arguments.length > 1) ? arguments[1] : null;
272
+ function findValueCallback(q, data) {
273
+ var result;
274
+ if( data && data.length ) {
275
+ for (var i=0; i < data.length; i++) {
276
+ if( data[i].result.toLowerCase() == q.toLowerCase() ) {
277
+ result = data[i];
278
+ break;
279
+ }
280
+ }
281
+ }
282
+ if( typeof fn == "function" ) {
283
+ fn(result);
284
+ } else {
285
+ $input.trigger("result", result && [result.data, result.value]);
286
+ }
287
+ }
288
+ $.each(trimWords(smartVal()), function(i, value) {
289
+ request(value, findValueCallback, findValueCallback);
290
+ });
291
+ }).bind("flushCache", function() {
292
+ cache.flush();
293
+ }).bind("setOptions", function() {
294
+ $.extend(options, arguments[1]);
295
+ // if we've updated the data, repopulate
296
+ if ( "data" in arguments[1] )
297
+ cache.populate();
298
+ }).bind("unautocomplete", function() {
299
+ select.unbind();
300
+ $input.unbind();
301
+ $(input.form).unbind(".autocomplete");
302
+ });
303
+
304
+
305
+ function selectCurrent() {
306
+ var selected = select.selected();
307
+ if( !selected )
308
+ return false;
309
+
310
+ var v = selected.result;
311
+ previousValue = v;
312
+
313
+ if ( options.multiple ) {
314
+ var words = trimWords(smartVal());
315
+ if ( words.length > 1 ) {
316
+ var seperator = options.multipleSeparator.length;
317
+ var cursorAt = $(input).selection().start;
318
+ var wordAt, progress = 0;
319
+ $.each(words, function(i, word) {
320
+ progress += word.length;
321
+ if (cursorAt <= progress) {
322
+ wordAt = i;
323
+ return false;
324
+ }
325
+ progress += seperator;
326
+ });
327
+ words[wordAt] = v;
328
+ // TODO this should set the cursor to the right position, but it gets overriden somewhere
329
+ //$.Autocompleter.Selection(input, progress + seperator, progress + seperator);
330
+ v = words.join( options.multipleSeparator );
331
+ }
332
+ v += options.multipleSeparator;
333
+
334
+ } else if ( options.hotkeymode) {
335
+ autocActive = false;
336
+ var cur = smartVal();
337
+ cur = cur.substring(0,cursorStart -1);
338
+ log.info("found Data! " + selected.data[0] + ' ' + selected.data[1])
339
+ v = cur + options.formatResult(selected.data);
340
+ if (input.value == undefined) {
341
+ v = v + '<span id="cursorStart">�</span>';
342
+ }
343
+
344
+ //storeContentEditableCursor();
345
+ }
346
+ smartVal(v);
347
+ hideResultsNow();
348
+ $input.trigger("result", [selected.data, selected.value]);
349
+ if ( options.hotkeymode && input.value == undefined)
350
+ editableReturnCursor();
351
+ return true;
352
+ }
353
+
354
+ function setupContentEditable(){
355
+ if(editableSelection.getRangeAt !== undefined) {
356
+ //ok
357
+ // Get range (Safari 2)
358
+ } else if(
359
+ document.createRange &&
360
+ editableSelection.anchorNode &&
361
+ editableSelection.anchorOffset &&
362
+ editableSelection.focusNode &&
363
+ editableSelection.focusOffset
364
+ ) {
365
+ var temp = '';
366
+ }
367
+ }
368
+
369
+ function storeContentEditableCursor() {
370
+ // editable is the contentEditable div
371
+
372
+ // Don't capture selection outside editable region
373
+ var isOrContainsAnchor = false,
374
+ isOrContainsFocus = false,
375
+ sel = window.getSelection(),
376
+ parentAnchor = sel.anchorNode,
377
+ parentFocus = sel.focusNode;
378
+
379
+ while(parentAnchor && parentAnchor != document.documentElement) {
380
+ if(parentAnchor == editable) {
381
+ isOrContainsAnchor = true;
382
+ }
383
+ parentAnchor = parentAnchor.parentNode;
384
+ }
385
+
386
+ while(parentFocus && parentFocus != document.documentElement) {
387
+ if(parentFocus == editable) {
388
+ isOrContainsFocus = true;
389
+ }
390
+ parentFocus = parentFocus.parentNode;
391
+ }
392
+
393
+ if(!isOrContainsAnchor || !isOrContainsFocus) {
394
+ return;
395
+ }
396
+
397
+ editableSelection = window.getSelection();
398
+
399
+ //editableSelection, editableRange;
400
+ // Get range (standards)
401
+ if(editableSelection.getRangeAt !== undefined) {
402
+ editableRange = editableSelection.getRangeAt(0);
403
+ //log.debug("in contenteditable keyup")
404
+ // Get range (Safari 2)
405
+ } else if(
406
+ document.createRange &&
407
+ editableSelection.anchorNode &&
408
+ editableSelection.anchorOffset &&
409
+ editableSelection.focusNode &&
410
+ editableSelection.focusOffset
411
+ ) {
412
+ editableRange = document.createRange();
413
+ editableRange.setStart(selection.anchorNode, editableSelection.anchorOffset);
414
+ editableRange.setEnd(selection.focusNode, editableSelection.focusOffset);
415
+ } else {
416
+ // Failure here, not handled by the rest of the script.
417
+ // Probably IE or some older browser
418
+ // TODO: gracefully degrate to textarea?
419
+ }
420
+
421
+ var cursorStartSpan = document.createElement('span'),
422
+ collapsed = !!editableRange.collapsed;
423
+
424
+ cursorStartSpan.id = 'cursorStart';
425
+ cursorStartSpan.appendChild(document.createTextNode('�'));
426
+
427
+ // Insert beginning cursor marker
428
+ editableRange.insertNode(cursorStartSpan);
429
+
430
+ // Insert end cursor marker if any text is selected
431
+ if(!collapsed) {
432
+ var cursorEnd = document.createElement('span');
433
+ cursorEnd.id = 'cursorEnd';
434
+ editableRange.collapse();
435
+ editableRange.insertNode(cursorEnd);
436
+ }
437
+ }
438
+
439
+ function editableReturnCursor(){
440
+ // Slight delay will avoid the initial selection
441
+ // (at start or of contents depending on browser) being mistaken
442
+ setTimeout(function() {
443
+ var cursorStartSpan = document.getElementById('cursorStart'),
444
+ cursorEnd = document.getElementById('cursorEnd');
445
+
446
+ if (window.getSelection) { // Firefox, Safari, Opera
447
+ editableSelection = window.getSelection();
448
+ var range = document.createRange();
449
+ range.selectNode(cursorStartSpan);
450
+ // Select range
451
+ editableSelection.removeAllRanges();
452
+ editableSelection.addRange(range);
453
+ // Delete cursor marker
454
+ document.execCommand('delete', false, null);
455
+ } else {
456
+ if (document.body.createTextRange) { // Internet Explorer
457
+ var rangeToSelect = document.body.createTextRange();
458
+ rangeToSelect.moveToElementText(cursorStartSpan);
459
+ rangeToSelect.select();
460
+ document.selection.clear();
461
+ }
462
+ }
463
+
464
+
465
+ // Register selection again
466
+ //captureSelection();
467
+ }, 10);
468
+ }
469
+
470
+ function onChange(crap, skipPrevCheck) {
471
+ if( lastKeyPressCode == KEY.DEL ) {
472
+ select.hide();
473
+ return;
474
+ }
475
+
476
+ var currentValue = smartVal();
477
+ if ( !skipPrevCheck && currentValue == previousValue ){
478
+ return;
479
+ }
480
+
481
+ currentValue = findSearchTerm(currentValue);
482
+ previousValue = currentValue;
483
+ log.debug("onChange curVal = " + currentValue)
484
+
485
+ if ( currentValue.length >= options.minChars) {
486
+ $input.addClass(options.loadingClass);
487
+ if (!options.matchCase)
488
+ currentValue = currentValue.toLowerCase();
489
+ request(currentValue, receiveData, hideResultsNow);
490
+ } else {
491
+ log.debug("in else")
492
+ stopLoading();
493
+ if (options.startmsg != null) {
494
+ select.emptyList();
495
+ select.display({}, null);
496
+ select.show();
497
+ } else {
498
+ select.hide();
499
+ }
500
+ }
501
+ };
502
+
503
+ function trimWords(value) {
504
+ if (!value)
505
+ return [""];
506
+ if (!options.multiple && !options.hotkeymode) {
507
+ return [$.trim(value)];
508
+ } else if (options.multiple) {
509
+ return $.map(value.split(options.multipleSeparator), function(word) {
510
+ return $.trim(value).length ? $.trim(word) : null;
511
+ });
512
+ } else if (options.hotkeymode) {
513
+ log.error("should not get here, remove this section, not user")
514
+ return fake.raise.error;
515
+ }
516
+ }
517
+
518
+ // find word currently being searched for, anything after
519
+ // previous results, or non query text
520
+ function findSearchTerm(value) {
521
+ if ( !options.multiple && !options.hotkeymode) {
522
+ return value;
523
+ } else if (options.multiple) {
524
+ var words = trimWords(value);
525
+ if (words.length == 1)
526
+ return words[0];
527
+ var cursorAt = $(input).selection().start;
528
+ if (cursorAt == value.length) {
529
+ words = trimWords(value)
530
+ } else {
531
+ words = trimWords(value.replace(value.substring(cursorAt), ""));
532
+ }
533
+ return words[words.length - 1];
534
+ // hotkeymode
535
+ } else {
536
+ if (value && value.lastIndexOf('@') >= 0){
537
+ cursorStart = value.lastIndexOf('@') + 1;
538
+ } else {
539
+ log.error("found no @ in hotkeymode?" + value)
540
+ cursorStart = value.length + 2;
541
+ }
542
+ value = value.substring(cursorStart);
543
+ log.info("findSearchTerm: cursorStart,val " + cursorStart + ', ' + value)
544
+ return value; //.trim();
545
+ }
546
+ }
547
+
548
+ // fills in the input box w/the first match (assumed to be the best match)
549
+ // q: the term entered
550
+ // sValue: the first matching result
551
+ function autoFill(q, sValue){
552
+ // autofill in the complete box w/the first match as long as the user hasn't entered in more data
553
+ // if the last user key pressed was backspace, don't autofill
554
+ if( options.autoFill && (findSearchTerm(smartVal()).toLowerCase() == q.toLowerCase()) && lastKeyPressCode != KEY.BACKSPACE ) {
555
+ // fill in the value (keep the case the user has typed)
556
+ smartVal(smartVal() + sValue.substring(findSearchTerm(previousValue).length));
557
+ // select the portion of the value not typed by the user (so the next character will erase)
558
+ $(input).selection(previousValue.length, previousValue.length + sValue.length);
559
+ }
560
+ };
561
+
562
+ // replace .val() with something to handle content-editable fields
563
+ function smartVal(val) {
564
+ var field = $input[0];
565
+ //if form input field, vs contenteditable div
566
+ if (field.value != undefined){
567
+ if (val != undefined) {
568
+ return $input.val(val);
569
+ } else {
570
+ return $input.val();
571
+ }
572
+ } else {
573
+ if (val != undefined) {
574
+ return $input.html(val);
575
+ } else {
576
+ val = $input.html();
577
+ if (val != undefined && val.length != undefined && val.length > 0) {
578
+ val = $.trim(val);
579
+ } else {
580
+ val = ''
581
+ }
582
+ var endval = '', li = 0;
583
+ // replace <br> or &nbsp; BUT, only in last bit (10) or past hotkey
584
+ // contenteditable appends <br> to end a lot, or the last space as &nbsp;
585
+ if (val.lastIndexOf('@') > 0 || val.length > 10){
586
+ li = val.lastIndexOf('@') > 0 ? val.lastIndexOf('@') + 1 : val.length - 10;
587
+ endval = val.substring(li);
588
+ val = val.substring(0,val.length - endval.length);
589
+ //log.debug('used substring @ ' + val)
590
+ } else {
591
+ endval = val;
592
+ val = '';
593
+ }
594
+ // only clean up end of markup, where they are doing insertion?
595
+ endval = endval.replace("<br>",'').replace("<br/>",'').replace("&nbsp;",' ');
596
+ val = val + endval;
597
+ //log.debug("smartVal endval = " + escape(endval) + ' val= ' + escape(val))
598
+ return val;
599
+ }
600
+ }
601
+ }
602
+
603
+ function hideResults() {
604
+ clearTimeout(timeout);
605
+ timeout = setTimeout(hideResultsNow, 200);
606
+ };
607
+
608
+ function hideResultsNow() {
609
+ var wasVisible = select.visible();
610
+ autocActive = false;
611
+ select.hide();
612
+ clearTimeout(timeout);
613
+ stopLoading();
614
+ if (options.mustMatch) {
615
+ // call search and run callback
616
+ $input.search(
617
+ function (result){
618
+ // if no value found, clear the input box
619
+ if( !result ) {
620
+ if (options.multiple) {
621
+ var words = trimWords(smartVal()).slice(0, -1);
622
+ smartVal( words.join(options.multipleSeparator) + (words.length ? options.multipleSeparator : "") );
623
+ } else if (options.hotkeymode) {
624
+ smartVal( "" );
625
+ $input.trigger("result", null);
626
+ } else {
627
+ smartVal( "" );
628
+ $input.trigger("result", null);
629
+ }
630
+ }
631
+ }
632
+ );
633
+ }
634
+ };
635
+
636
+ function receiveData(q, data) {
637
+ if ( data && data.length && hasFocus ) {
638
+ stopLoading();
639
+ select.display(data, q);
640
+ autoFill(q, data[0].value);
641
+ select.show();
642
+ } else {
643
+ hideResultsNow();
644
+ }
645
+ };
646
+
647
+ function request(term, success, failure) {
648
+ log.debug("in request term = " + term)
649
+ if (!options.matchCase)
650
+ term = term.toLowerCase();
651
+
652
+ log.debug("in request term2 = " + term)
653
+ var data = cache.load(term);
654
+ log.debug("in request term3 = " + data)
655
+ // recieve the cached data
656
+ if (data && data.length) {
657
+ log.debug('found cache, not loading ' + term)
658
+ success(term, data);
659
+ return;
660
+ // if an AJAX url has been supplied, try loading the data now
661
+ } else if( (typeof options.url == "string") && (options.url.length > 0) ){
662
+
663
+ var extraParams = {
664
+ timestamp: +new Date()
665
+ };
666
+ $.each(options.extraParams, function(key, param) {
667
+ extraParams[key] = typeof param == "function" ? param() : param;
668
+ });
669
+ var found = false;
670
+ log.debug("calling ajax, term = " + term, ' dataType = ' + options.dataType)
671
+ $.ajax({
672
+ // try to leverage ajaxQueue plugin to abort previous requests
673
+ mode: "abort",
674
+ // limit abortion to this input
675
+ port: "autocomplete" + input.name,
676
+ dataType: options.dataType,
677
+ url: options.url,
678
+ data: $.extend({
679
+ q: term,
680
+ limit: options.max
681
+ }, extraParams),
682
+ success: function(data) {
683
+ if (data.length > 0){
684
+ found = true;
685
+ var parsed = options.parse && options.parse(data) || parse_json(data);
686
+ cache.add(term, parsed);
687
+ success(term, parsed);
688
+ }
689
+ }
690
+ });
691
+ if (found === true){
692
+ log.debug("returning?")
693
+ return;
694
+ }
695
+ }
696
+ log.debug("after load in request?")
697
+ select.emptyList();
698
+ if (options.noresultsmsg != null) {
699
+ stopLoading();
700
+ select.display({}, term);
701
+ select.show();
702
+ } else {
703
+ // if we have a failure, we need to empty the list -- this prevents the the [TAB] key from selecting the last successful match
704
+ failure(term);
705
+ }
706
+ };
707
+
708
+ function parse_json(json){
709
+ var parsed = [];
710
+ log.debug("parsing json, len=" + json.length)
711
+ for ( var i = 0, ol = json.length; i < ol; i++ ) {
712
+ parsed[i] = {
713
+ data: json[i],
714
+ value: json[i][options.jsonterm],
715
+ result: options.formatResult && options.formatResult(json[i]) || json[i][options.jsonterm]
716
+ };
717
+ }
718
+ return parsed;
719
+ }
720
+
721
+ function stopLoading() {
722
+ $input.removeClass(options.loadingClass);
723
+ };
724
+
725
+ };
726
+
727
+ $.Autocompleter.defaults = {
728
+ inputClass: "ac_input",
729
+ resultsClass: "ac_results",
730
+ loadingClass: "ac_loading",
731
+ minChars: 1,
732
+ live:false,
733
+ startmsg: 'Start typing to get options...',
734
+ msgonenter:false,
735
+ endmsg: null,
736
+ noresultsmsg: null,
737
+ delay: 400,
738
+ matchCase: false,
739
+ matchSubset: true,
740
+ matchContains: false,
741
+ cacheLength: 10,
742
+ supressReturn: false,
743
+ max: 100,
744
+ mustMatch: false,
745
+ extraParams: {},
746
+ jsonterm: 0, // 'name' ??
747
+ formatResult: null,//placeholder, function in above options
748
+ formatItem: null, //placeholder, function in above options
749
+ dataType: 'json',
750
+ selectFirst: true,
751
+ formatMatch: null,
752
+ autoFill: false,
753
+ width: 0,
754
+ left: 0,
755
+ multiple: false,
756
+ multipleSeparator: ", ",
757
+ hotkeymode: false,
758
+ highlight: function(value, term) {
759
+ return value.replace(new RegExp("(?![^&;]+;)(?!<[^<>]*)(" + term.replace(/([\^\$\(\)\[\]\{\}\*\.\+\?\|\\])/gi, "\\$1") + ")(?![^<>]*>)(?![^&;]+;)", "gi"), "<strong>$1</strong>");
760
+ },
761
+ scroll: true,
762
+ scrollHeight: 180
763
+ };
764
+
765
+ $.Autocompleter.Cache = function(options) {
766
+
767
+ var data = {};
768
+ var length = 0;
769
+
770
+ function matchSubset(s, sub) {
771
+ if (!options.matchCase)
772
+ s = s.toLowerCase();
773
+ var i = s.indexOf(sub);
774
+ if (options.matchContains == "word"){
775
+ i = s.toLowerCase().search("\\b" + sub.toLowerCase());
776
+ }
777
+ if (i == -1) return false;
778
+ return i == 0 || options.matchContains;
779
+ };
780
+
781
+ function add(q, value) {
782
+ if (length > options.cacheLength){
783
+ flush();
784
+ }
785
+ if (!data[q]){
786
+ length++;
787
+ }
788
+ data[q] = value;
789
+ }
790
+
791
+ function populate(){
792
+ if( !options.data ) return false;
793
+ // track the matches
794
+ var stMatchSets = {},
795
+ nullData = 0;
796
+
797
+ // no url was specified, we need to adjust the cache length to make sure it fits the local data store
798
+ if( !options.url ) options.cacheLength = 1;
799
+
800
+ // track all options for minChars = 0
801
+ stMatchSets[""] = [];
802
+
803
+ // loop through the array and create a lookup structure
804
+ for ( var i = 0, ol = options.data.length; i < ol; i++ ) {
805
+ var rawValue = options.data[i];
806
+ // if rawValue is a string, make an array otherwise just reference the array
807
+ rawValue = (typeof rawValue == "string") ? [rawValue] : rawValue;
808
+
809
+ var value = options.formatMatch(rawValue, i+1, options.data.length);
810
+ if ( value === false || value === undefined)
811
+ continue;
812
+
813
+ var firstChar = value.charAt(0).toLowerCase();
814
+ // if no lookup array for this character exists, look it up now
815
+ if( !stMatchSets[firstChar] )
816
+ stMatchSets[firstChar] = [];
817
+
818
+ // if the match is a string
819
+ var row = {
820
+ value: value,
821
+ data: rawValue,
822
+ result: options.formatResult && options.formatResult(rawValue) || value
823
+ };
824
+
825
+ // push the current match into the set list
826
+ stMatchSets[firstChar].push(row);
827
+
828
+ // keep track of minChars zero items
829
+ if ( nullData++ < options.max ) {
830
+ stMatchSets[""].push(row);
831
+ }
832
+ };
833
+
834
+ // add the data items to the cache
835
+ $.each(stMatchSets, function(i, value) {
836
+ // increase the cache size
837
+ options.cacheLength++;
838
+ // add to the cache
839
+ add(i, value);
840
+ });
841
+ }
842
+
843
+ // populate any existing data
844
+ setTimeout(populate, 25);
845
+
846
+ function flush(){
847
+ data = {};
848
+ length = 0;
849
+ }
850
+
851
+ return {
852
+ flush: flush,
853
+ add: add,
854
+ populate: populate,
855
+ load: function(q) {
856
+ if (!options.cacheLength || !length)
857
+ return null;
858
+ /*
859
+ * if dealing w/local data and matchContains than we must make sure
860
+ * to loop through all the data collections looking for matches
861
+ */
862
+ if( !options.url && options.matchContains ){
863
+ // track all matches
864
+ var csub = [];
865
+ // loop through all the data grids for matches
866
+ for( var k in data ){
867
+ // don't search through the stMatchSets[""] (minChars: 0) cache
868
+ // this prevents duplicates
869
+ if( k.length > 0 ){
870
+ var c = data[k];
871
+ //log.debug("629 " + c + ' ' + k + ' ' + q)
872
+ $.each(c, function(i, x) {
873
+ // if we've got a match, add it to the array
874
+ if (matchSubset(x.value, q)) {
875
+ csub.push(x);
876
+ }
877
+ });
878
+ }
879
+ }
880
+ return csub;
881
+ } else
882
+ // if the exact item exists, use it
883
+ if (data[q]){
884
+ return data[q];
885
+ } else
886
+ if (options.matchSubset) {
887
+ for (var i = q.length - 1; i >= options.minChars; i--) {
888
+ var c = data[q.substr(0, i)];
889
+ if (c) {
890
+ var csub = [];
891
+ $.each(c, function(i, x) {
892
+ if (matchSubset(x.value, q)) {
893
+ csub[csub.length] = x;
894
+ }
895
+ });
896
+ return csub;
897
+ }
898
+ }
899
+ }
900
+ return null;
901
+ }
902
+ };
903
+ };
904
+
905
+ $.Autocompleter.Select = function (options, input, select, config) {
906
+ var CLASSES = {
907
+ ACTIVE: "ac_over"
908
+ };
909
+
910
+ var listItems,
911
+ active = -1,
912
+ data,
913
+ term = "",
914
+ needsInit = true,
915
+ element,
916
+ list;
917
+
918
+ // Create results
919
+ function init() {
920
+ if (!needsInit)
921
+ return;
922
+ element = $("<div/>")
923
+ .hide()
924
+ .addClass(options.resultsClass)
925
+ .css("position", "absolute")
926
+ .appendTo(document.body);
927
+ list = $("<ul/>").appendTo(element).css({
928
+ width: typeof options.width == "string" || options.width > 0 ? options.width -2 : $(input).width() -2
929
+ }).mouseover( function(event) {
930
+ if(target(event).nodeName && target(event).nodeName.toUpperCase() == 'LI') {
931
+ active = $("li", list).removeClass(CLASSES.ACTIVE).index(target(event));
932
+ $(target(event)).addClass(CLASSES.ACTIVE);
933
+ }
934
+ }).click(function(event) {
935
+ $(target(event)).addClass(CLASSES.ACTIVE);
936
+ select();
937
+ // TODO provide option to avoid setting focus again after selection? useful for cleanup-on-focus
938
+ input.focus();
939
+ return false;
940
+ }).mousedown(function() {
941
+ config.mouseDownOnSelect = true;
942
+ }).mouseup(function() {
943
+ config.mouseDownOnSelect = false;
944
+ });
945
+
946
+ if( options.width > 0 )
947
+ element.css("width", options.width);
948
+
949
+ needsInit = false;
950
+ }
951
+
952
+ function target(event) {
953
+ var element = event.target;
954
+ while(element && element.tagName != "LI")
955
+ element = element.parentNode;
956
+ // more fun with IE, sometimes event.target is empty, just ignore it then
957
+ if(!element)
958
+ return [];
959
+ return element;
960
+ }
961
+
962
+ function moveSelect(step) {
963
+ listItems.slice(active, active + 1).removeClass(CLASSES.ACTIVE);
964
+ movePosition(step);
965
+ var activeItem = listItems.slice(active, active + 1).addClass(CLASSES.ACTIVE);
966
+ if(options.scroll) {
967
+ var offset = 0;
968
+ listItems.slice(0, active).each(function() {
969
+ offset += this.offsetHeight;
970
+ });
971
+ if((offset + activeItem[0].offsetHeight - list.scrollTop()) > list[0].clientHeight) {
972
+ list.scrollTop(offset + activeItem[0].offsetHeight - list.innerHeight());
973
+ } else if(offset < list.scrollTop()) {
974
+ list.scrollTop(offset);
975
+ }
976
+ }
977
+ };
978
+
979
+ function movePosition(step) {
980
+ active += step;
981
+ if (active < 0) {
982
+ active = listItems.size() - 1;
983
+ } else if (active >= listItems.size()) {
984
+ active = 0;
985
+ }
986
+ }
987
+
988
+ function limitNumberOfItems(available) {
989
+ return options.max && options.max < available
990
+ ? options.max
991
+ : available;
992
+ }
993
+
994
+ function fillList(q) {
995
+ list.empty();
996
+ var max = limitNumberOfItems(data.length);
997
+ for (var i=0; i < max; i++) {
998
+ if (!data[i])
999
+ continue;
1000
+ var formatted = options.formatItem(data[i].data, i+1, max, data[i].value, term);
1001
+ if ( formatted === false )
1002
+ continue;
1003
+ var li = $("<li/>").html( options.highlight(formatted, term) ).addClass(i%2 == 0 ? "ac_even" : "ac_odd").appendTo(list)[0];
1004
+ $.data(li, "ac_data", data[i]);
1005
+ }
1006
+ listItems = list.find("li");
1007
+ if ( options.selectFirst ) {
1008
+ listItems.slice(0, 1).addClass(CLASSES.ACTIVE);
1009
+ active = 0;
1010
+ }
1011
+ if (options.startmsg && (max == 0 || max == undefined) && q == null) {
1012
+ var li = $("<li/>").html( options.startmsg )
1013
+ .click(function(){
1014
+ $(input).trigger("ac_start_msg_click");
1015
+ }).addClass("start_msg").appendTo(list)[0];
1016
+ $.data(li, "start_msg", data[max + 1]);
1017
+ }
1018
+ if (options.noresultsmsg && (max == 0 || max == undefined) && q != null) {
1019
+ var val_noresult = options.noresultsmsg;
1020
+ if( typeof options.noresultsmsg == "function" ) {
1021
+ val_noresult = fn(q);
1022
+ } else if (options.noresultsmsg.indexOf('{q}' > 0)) {
1023
+ val_noresult = val_noresult.replace('{q}',q);
1024
+ }
1025
+ var li = $("<li/>").html( val_noresult )
1026
+ .click(function(){
1027
+ $(input).trigger("ac_noresult_click",q);
1028
+ }).addClass("noresult_msg").appendTo(list)[0];
1029
+ $.data(li, "noresult_msg", data[max + 1]);
1030
+ }
1031
+ if (options.endmsg && (max != undefined && max > 0)) {
1032
+ var li = $("<li/>").html( options.endmsg )
1033
+ .click(function(){
1034
+ $(input).trigger("ac_end_message_click",q);
1035
+ }).addClass("end_msg").appendTo(list)[0];
1036
+ $.data(li, "end_msg", data[max + 1]);
1037
+ }
1038
+ // apply bgiframe if available
1039
+ if ( $.fn.bgiframe )
1040
+ list.bgiframe();
1041
+ }
1042
+
1043
+ return {
1044
+ display: function(d, q) {
1045
+ init();
1046
+ data = d;
1047
+ term = q;
1048
+ fillList(q);
1049
+ },
1050
+ next: function() {
1051
+ moveSelect(1);
1052
+ },
1053
+ prev: function() {
1054
+ moveSelect(-1);
1055
+ },
1056
+ pageUp: function() {
1057
+ if (active != 0 && active - 8 < 0) {
1058
+ moveSelect( -active );
1059
+ } else {
1060
+ moveSelect(-8);
1061
+ }
1062
+ },
1063
+ pageDown: function() {
1064
+ if (active != listItems.size() - 1 && active + 8 > listItems.size()) {
1065
+ moveSelect( listItems.size() - 1 - active );
1066
+ } else {
1067
+ moveSelect(8);
1068
+ }
1069
+ },
1070
+ hide: function() {
1071
+ log.info("hiding?")
1072
+ element && element.hide();
1073
+ listItems && listItems.removeClass(CLASSES.ACTIVE);
1074
+ active = -1;
1075
+ },
1076
+ visible : function() {
1077
+ return element && element.is(":visible");
1078
+ },
1079
+ current: function() {
1080
+ return this.visible() && (listItems.filter("." + CLASSES.ACTIVE)[0] || options.selectFirst && listItems[0]);
1081
+ },
1082
+ show: function() {
1083
+ var offset = $(input).offset();
1084
+ element.css({
1085
+ width: typeof options.width == "string" || options.width > 0 ? options.width : $(input).width(),
1086
+ top: offset.top + input.offsetHeight,
1087
+ left: options.left > 0 ? options.left : offset.left
1088
+ }).show();
1089
+
1090
+ if(options.scroll) {
1091
+ list.scrollTop(0);
1092
+ list.css({
1093
+ maxHeight: options.scrollHeight,
1094
+ overflow: 'auto'
1095
+ });
1096
+
1097
+ if($.browser.msie && typeof document.body.style.maxHeight === "undefined") {
1098
+ var listHeight = 0;
1099
+ listItems.each(function() {
1100
+ listHeight += this.offsetHeight;
1101
+ });
1102
+ var scrollbarsVisible = listHeight > options.scrollHeight;
1103
+ list.css('height', scrollbarsVisible ? options.scrollHeight : listHeight );
1104
+ if (!scrollbarsVisible) {
1105
+ // IE doesn't recalculate width when scrollbar disappears
1106
+ listItems.width( list.width() - parseInt(listItems.css("padding-left")) - parseInt(listItems.css("padding-right")) );
1107
+ }
1108
+ }
1109
+ }
1110
+ },
1111
+ selected: function() {
1112
+ var selected = listItems && listItems.filter("." + CLASSES.ACTIVE).removeClass(CLASSES.ACTIVE);
1113
+ return selected && selected.length && $.data(selected[0], "ac_data");
1114
+ },
1115
+ emptyList: function (){
1116
+ list && list.empty();
1117
+ },
1118
+ unbind: function() {
1119
+ element && element.remove();
1120
+ }
1121
+ };
1122
+ };
1123
+ $.fn.selection = function(start, end) {
1124
+ if (start !== undefined) {
1125
+ log.debug("in selection, no start/end")
1126
+ return this.each(function() {
1127
+ if( this.createTextRange ){
1128
+ var selRange = this.createTextRange();
1129
+ if (end === undefined || start == end) {
1130
+ selRange.move("character", start);
1131
+ selRange.select();
1132
+ } else {
1133
+ selRange.collapse(true);
1134
+ selRange.moveStart("character", start);
1135
+ selRange.moveEnd("character", end);
1136
+ selRange.select();
1137
+ }
1138
+ } else if( this.setSelectionRange ){
1139
+ this.setSelectionRange(start, end);
1140
+ } else if( this.selectionStart ){
1141
+ this.selectionStart = start;
1142
+ this.selectionEnd = end;
1143
+ }
1144
+ });
1145
+ }
1146
+ var field = this[0];
1147
+ if ( field.createTextRange ) {
1148
+ log.debug("in selection with createTextRange")
1149
+ var range = document.selection.createRange(),
1150
+ orig = field.value,
1151
+ teststring = "<->",
1152
+ textLength = range.text.length;
1153
+ range.text = teststring;
1154
+ var caretAt = field.value.indexOf(teststring);
1155
+ field.value = orig;
1156
+ this.selection(caretAt, caretAt + textLength);
1157
+ return {
1158
+ start: caretAt,
1159
+ end: caretAt + textLength
1160
+ }
1161
+ } else if( field.selectionStart !== undefined ){
1162
+ return {
1163
+ start: field.selectionStart,
1164
+ end: field.selectionEnd
1165
+ }
1166
+ } else {
1167
+ if(field.getRangeAt !== undefined) {
1168
+ log.error("ah, getRangeAt, ?")
1169
+ } else {
1170
+ log.error("ah, none of above, ?")
1171
+ }
1172
+
1173
+ }
1174
+ };
1175
+
1176
+ })(jQuery);
license.txt DELETED
@@ -1,674 +0,0 @@
1
- GNU GENERAL PUBLIC LICENSE
2
- Version 3, 29 June 2007
3
-
4
- Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
5
- Everyone is permitted to copy and distribute verbatim copies
6
- of this license document, but changing it is not allowed.
7
-
8
- Preamble
9
-
10
- The GNU General Public License is a free, copyleft license for
11
- software and other kinds of works.
12
-
13
- The licenses for most software and other practical works are designed
14
- to take away your freedom to share and change the works. By contrast,
15
- the GNU General Public License is intended to guarantee your freedom to
16
- share and change all versions of a program--to make sure it remains free
17
- software for all its users. We, the Free Software Foundation, use the
18
- GNU General Public License for most of our software; it applies also to
19
- any other work released this way by its authors. You can apply it to
20
- your programs, too.
21
-
22
- When we speak of free software, we are referring to freedom, not
23
- price. Our General Public Licenses are designed to make sure that you
24
- have the freedom to distribute copies of free software (and charge for
25
- them if you wish), that you receive source code or can get it if you
26
- want it, that you can change the software or use pieces of it in new
27
- free programs, and that you know you can do these things.
28
-
29
- To protect your rights, we need to prevent others from denying you
30
- these rights or asking you to surrender the rights. Therefore, you have
31
- certain responsibilities if you distribute copies of the software, or if
32
- you modify it: responsibilities to respect the freedom of others.
33
-
34
- For example, if you distribute copies of such a program, whether
35
- gratis or for a fee, you must pass on to the recipients the same
36
- freedoms that you received. You must make sure that they, too, receive
37
- or can get the source code. And you must show them these terms so they
38
- know their rights.
39
-
40
- Developers that use the GNU GPL protect your rights with two steps:
41
- (1) assert copyright on the software, and (2) offer you this License
42
- giving you legal permission to copy, distribute and/or modify it.
43
-
44
- For the developers' and authors' protection, the GPL clearly explains
45
- that there is no warranty for this free software. For both users' and
46
- authors' sake, the GPL requires that modified versions be marked as
47
- changed, so that their problems will not be attributed erroneously to
48
- authors of previous versions.
49
-
50
- Some devices are designed to deny users access to install or run
51
- modified versions of the software inside them, although the manufacturer
52
- can do so. This is fundamentally incompatible with the aim of
53
- protecting users' freedom to change the software. The systematic
54
- pattern of such abuse occurs in the area of products for individuals to
55
- use, which is precisely where it is most unacceptable. Therefore, we
56
- have designed this version of the GPL to prohibit the practice for those
57
- products. If such problems arise substantially in other domains, we
58
- stand ready to extend this provision to those domains in future versions
59
- of the GPL, as needed to protect the freedom of users.
60
-
61
- Finally, every program is threatened constantly by software patents.
62
- States should not allow patents to restrict development and use of
63
- software on general-purpose computers, but in those that do, we wish to
64
- avoid the special danger that patents applied to a free program could
65
- make it effectively proprietary. To prevent this, the GPL assures that
66
- patents cannot be used to render the program non-free.
67
-
68
- The precise terms and conditions for copying, distribution and
69
- modification follow.
70
-
71
- TERMS AND CONDITIONS
72
-
73
- 0. Definitions.
74
-
75
- "This License" refers to version 3 of the GNU General Public License.
76
-
77
- "Copyright" also means copyright-like laws that apply to other kinds of
78
- works, such as semiconductor masks.
79
-
80
- "The Program" refers to any copyrightable work licensed under this
81
- License. Each licensee is addressed as "you". "Licensees" and
82
- "recipients" may be individuals or organizations.
83
-
84
- To "modify" a work means to copy from or adapt all or part of the work
85
- in a fashion requiring copyright permission, other than the making of an
86
- exact copy. The resulting work is called a "modified version" of the
87
- earlier work or a work "based on" the earlier work.
88
-
89
- A "covered work" means either the unmodified Program or a work based
90
- on the Program.
91
-
92
- To "propagate" a work means to do anything with it that, without
93
- permission, would make you directly or secondarily liable for
94
- infringement under applicable copyright law, except executing it on a
95
- computer or modifying a private copy. Propagation includes copying,
96
- distribution (with or without modification), making available to the
97
- public, and in some countries other activities as well.
98
-
99
- To "convey" a work means any kind of propagation that enables other
100
- parties to make or receive copies. Mere interaction with a user through
101
- a computer network, with no transfer of a copy, is not conveying.
102
-
103
- An interactive user interface displays "Appropriate Legal Notices"
104
- to the extent that it includes a convenient and prominently visible
105
- feature that (1) displays an appropriate copyright notice, and (2)
106
- tells the user that there is no warranty for the work (except to the
107
- extent that warranties are provided), that licensees may convey the
108
- work under this License, and how to view a copy of this License. If
109
- the interface presents a list of user commands or options, such as a
110
- menu, a prominent item in the list meets this criterion.
111
-
112
- 1. Source Code.
113
-
114
- The "source code" for a work means the preferred form of the work
115
- for making modifications to it. "Object code" means any non-source
116
- form of a work.
117
-
118
- A "Standard Interface" means an interface that either is an official
119
- standard defined by a recognized standards body, or, in the case of
120
- interfaces specified for a particular programming language, one that
121
- is widely used among developers working in that language.
122
-
123
- The "System Libraries" of an executable work include anything, other
124
- than the work as a whole, that (a) is included in the normal form of
125
- packaging a Major Component, but which is not part of that Major
126
- Component, and (b) serves only to enable use of the work with that
127
- Major Component, or to implement a Standard Interface for which an
128
- implementation is available to the public in source code form. A
129
- "Major Component", in this context, means a major essential component
130
- (kernel, window system, and so on) of the specific operating system
131
- (if any) on which the executable work runs, or a compiler used to
132
- produce the work, or an object code interpreter used to run it.
133
-
134
- The "Corresponding Source" for a work in object code form means all
135
- the source code needed to generate, install, and (for an executable
136
- work) run the object code and to modify the work, including scripts to
137
- control those activities. However, it does not include the work's
138
- System Libraries, or general-purpose tools or generally available free
139
- programs which are used unmodified in performing those activities but
140
- which are not part of the work. For example, Corresponding Source
141
- includes interface definition files associated with source files for
142
- the work, and the source code for shared libraries and dynamically
143
- linked subprograms that the work is specifically designed to require,
144
- such as by intimate data communication or control flow between those
145
- subprograms and other parts of the work.
146
-
147
- The Corresponding Source need not include anything that users
148
- can regenerate automatically from other parts of the Corresponding
149
- Source.
150
-
151
- The Corresponding Source for a work in source code form is that
152
- same work.
153
-
154
- 2. Basic Permissions.
155
-
156
- All rights granted under this License are granted for the term of
157
- copyright on the Program, and are irrevocable provided the stated
158
- conditions are met. This License explicitly affirms your unlimited
159
- permission to run the unmodified Program. The output from running a
160
- covered work is covered by this License only if the output, given its
161
- content, constitutes a covered work. This License acknowledges your
162
- rights of fair use or other equivalent, as provided by copyright law.
163
-
164
- You may make, run and propagate covered works that you do not
165
- convey, without conditions so long as your license otherwise remains
166
- in force. You may convey covered works to others for the sole purpose
167
- of having them make modifications exclusively for you, or provide you
168
- with facilities for running those works, provided that you comply with
169
- the terms of this License in conveying all material for which you do
170
- not control copyright. Those thus making or running the covered works
171
- for you must do so exclusively on your behalf, under your direction
172
- and control, on terms that prohibit them from making any copies of
173
- your copyrighted material outside their relationship with you.
174
-
175
- Conveying under any other circumstances is permitted solely under
176
- the conditions stated below. Sublicensing is not allowed; section 10
177
- makes it unnecessary.
178
-
179
- 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
180
-
181
- No covered work shall be deemed part of an effective technological
182
- measure under any applicable law fulfilling obligations under article
183
- 11 of the WIPO copyright treaty adopted on 20 December 1996, or
184
- similar laws prohibiting or restricting circumvention of such
185
- measures.
186
-
187
- When you convey a covered work, you waive any legal power to forbid
188
- circumvention of technological measures to the extent such circumvention
189
- is effected by exercising rights under this License with respect to
190
- the covered work, and you disclaim any intention to limit operation or
191
- modification of the work as a means of enforcing, against the work's
192
- users, your or third parties' legal rights to forbid circumvention of
193
- technological measures.
194
-
195
- 4. Conveying Verbatim Copies.
196
-
197
- You may convey verbatim copies of the Program's source code as you
198
- receive it, in any medium, provided that you conspicuously and
199
- appropriately publish on each copy an appropriate copyright notice;
200
- keep intact all notices stating that this License and any
201
- non-permissive terms added in accord with section 7 apply to the code;
202
- keep intact all notices of the absence of any warranty; and give all
203
- recipients a copy of this License along with the Program.
204
-
205
- You may charge any price or no price for each copy that you convey,
206
- and you may offer support or warranty protection for a fee.
207
-
208
- 5. Conveying Modified Source Versions.
209
-
210
- You may convey a work based on the Program, or the modifications to
211
- produce it from the Program, in the form of source code under the
212
- terms of section 4, provided that you also meet all of these conditions:
213
-
214
- a) The work must carry prominent notices stating that you modified
215
- it, and giving a relevant date.
216
-
217
- b) The work must carry prominent notices stating that it is
218
- released under this License and any conditions added under section
219
- 7. This requirement modifies the requirement in section 4 to
220
- "keep intact all notices".
221
-
222
- c) You must license the entire work, as a whole, under this
223
- License to anyone who comes into possession of a copy. This
224
- License will therefore apply, along with any applicable section 7
225
- additional terms, to the whole of the work, and all its parts,
226
- regardless of how they are packaged. This License gives no
227
- permission to license the work in any other way, but it does not
228
- invalidate such permission if you have separately received it.
229
-
230
- d) If the work has interactive user interfaces, each must display
231
- Appropriate Legal Notices; however, if the Program has interactive
232
- interfaces that do not display Appropriate Legal Notices, your
233
- work need not make them do so.
234
-
235
- A compilation of a covered work with other separate and independent
236
- works, which are not by their nature extensions of the covered work,
237
- and which are not combined with it such as to form a larger program,
238
- in or on a volume of a storage or distribution medium, is called an
239
- "aggregate" if the compilation and its resulting copyright are not
240
- used to limit the access or legal rights of the compilation's users
241
- beyond what the individual works permit. Inclusion of a covered work
242
- in an aggregate does not cause this License to apply to the other
243
- parts of the aggregate.
244
-
245
- 6. Conveying Non-Source Forms.
246
-
247
- You may convey a covered work in object code form under the terms
248
- of sections 4 and 5, provided that you also convey the
249
- machine-readable Corresponding Source under the terms of this License,
250
- in one of these ways:
251
-
252
- a) Convey the object code in, or embodied in, a physical product
253
- (including a physical distribution medium), accompanied by the
254
- Corresponding Source fixed on a durable physical medium
255
- customarily used for software interchange.
256
-
257
- b) Convey the object code in, or embodied in, a physical product
258
- (including a physical distribution medium), accompanied by a
259
- written offer, valid for at least three years and valid for as
260
- long as you offer spare parts or customer support for that product
261
- model, to give anyone who possesses the object code either (1) a
262
- copy of the Corresponding Source for all the software in the
263
- product that is covered by this License, on a durable physical
264
- medium customarily used for software interchange, for a price no
265
- more than your reasonable cost of physically performing this
266
- conveying of source, or (2) access to copy the
267
- Corresponding Source from a network server at no charge.
268
-
269
- c) Convey individual copies of the object code with a copy of the
270
- written offer to provide the Corresponding Source. This
271
- alternative is allowed only occasionally and noncommercially, and
272
- only if you received the object code with such an offer, in accord
273
- with subsection 6b.
274
-
275
- d) Convey the object code by offering access from a designated
276
- place (gratis or for a charge), and offer equivalent access to the
277
- Corresponding Source in the same way through the same place at no
278
- further charge. You need not require recipients to copy the
279
- Corresponding Source along with the object code. If the place to
280
- copy the object code is a network server, the Corresponding Source
281
- may be on a different server (operated by you or a third party)
282
- that supports equivalent copying facilities, provided you maintain
283
- clear directions next to the object code saying where to find the
284
- Corresponding Source. Regardless of what server hosts the
285
- Corresponding Source, you remain obligated to ensure that it is
286
- available for as long as needed to satisfy these requirements.
287
-
288
- e) Convey the object code using peer-to-peer transmission, provided
289
- you inform other peers where the object code and Corresponding
290
- Source of the work are being offered to the general public at no
291
- charge under subsection 6d.
292
-
293
- A separable portion of the object code, whose source code is excluded
294
- from the Corresponding Source as a System Library, need not be
295
- included in conveying the object code work.
296
-
297
- A "User Product" is either (1) a "consumer product", which means any
298
- tangible personal property which is normally used for personal, family,
299
- or household purposes, or (2) anything designed or sold for incorporation
300
- into a dwelling. In determining whether a product is a consumer product,
301
- doubtful cases shall be resolved in favor of coverage. For a particular
302
- product received by a particular user, "normally used" refers to a
303
- typical or common use of that class of product, regardless of the status
304
- of the particular user or of the way in which the particular user
305
- actually uses, or expects or is expected to use, the product. A product
306
- is a consumer product regardless of whether the product has substantial
307
- commercial, industrial or non-consumer uses, unless such uses represent
308
- the only significant mode of use of the product.
309
-
310
- "Installation Information" for a User Product means any methods,
311
- procedures, authorization keys, or other information required to install
312
- and execute modified versions of a covered work in that User Product from
313
- a modified version of its Corresponding Source. The information must
314
- suffice to ensure that the continued functioning of the modified object
315
- code is in no case prevented or interfered with solely because
316
- modification has been made.
317
-
318
- If you convey an object code work under this section in, or with, or
319
- specifically for use in, a User Product, and the conveying occurs as
320
- part of a transaction in which the right of possession and use of the
321
- User Product is transferred to the recipient in perpetuity or for a
322
- fixed term (regardless of how the transaction is characterized), the
323
- Corresponding Source conveyed under this section must be accompanied
324
- by the Installation Information. But this requirement does not apply
325
- if neither you nor any third party retains the ability to install
326
- modified object code on the User Product (for example, the work has
327
- been installed in ROM).
328
-
329
- The requirement to provide Installation Information does not include a
330
- requirement to continue to provide support service, warranty, or updates
331
- for a work that has been modified or installed by the recipient, or for
332
- the User Product in which it has been modified or installed. Access to a
333
- network may be denied when the modification itself materially and
334
- adversely affects the operation of the network or violates the rules and
335
- protocols for communication across the network.
336
-
337
- Corresponding Source conveyed, and Installation Information provided,
338
- in accord with this section must be in a format that is publicly
339
- documented (and with an implementation available to the public in
340
- source code form), and must require no special password or key for
341
- unpacking, reading or copying.
342
-
343
- 7. Additional Terms.
344
-
345
- "Additional permissions" are terms that supplement the terms of this
346
- License by making exceptions from one or more of its conditions.
347
- Additional permissions that are applicable to the entire Program shall
348
- be treated as though they were included in this License, to the extent
349
- that they are valid under applicable law. If additional permissions
350
- apply only to part of the Program, that part may be used separately
351
- under those permissions, but the entire Program remains governed by
352
- this License without regard to the additional permissions.
353
-
354
- When you convey a copy of a covered work, you may at your option
355
- remove any additional permissions from that copy, or from any part of
356
- it. (Additional permissions may be written to require their own
357
- removal in certain cases when you modify the work.) You may place
358
- additional permissions on material, added by you to a covered work,
359
- for which you have or can give appropriate copyright permission.
360
-
361
- Notwithstanding any other provision of this License, for material you
362
- add to a covered work, you may (if authorized by the copyright holders of
363
- that material) supplement the terms of this License with terms:
364
-
365
- a) Disclaiming warranty or limiting liability differently from the
366
- terms of sections 15 and 16 of this License; or
367
-
368
- b) Requiring preservation of specified reasonable legal notices or
369
- author attributions in that material or in the Appropriate Legal
370
- Notices displayed by works containing it; or
371
-
372
- c) Prohibiting misrepresentation of the origin of that material, or
373
- requiring that modified versions of such material be marked in
374
- reasonable ways as different from the original version; or
375
-
376
- d) Limiting the use for publicity purposes of names of licensors or
377
- authors of the material; or
378
-
379
- e) Declining to grant rights under trademark law for use of some
380
- trade names, trademarks, or service marks; or
381
-
382
- f) Requiring indemnification of licensors and authors of that
383
- material by anyone who conveys the material (or modified versions of
384
- it) with contractual assumptions of liability to the recipient, for
385
- any liability that these contractual assumptions directly impose on
386
- those licensors and authors.
387
-
388
- All other non-permissive additional terms are considered "further
389
- restrictions" within the meaning of section 10. If the Program as you
390
- received it, or any part of it, contains a notice stating that it is
391
- governed by this License along with a term that is a further
392
- restriction, you may remove that term. If a license document contains
393
- a further restriction but permits relicensing or conveying under this
394
- License, you may add to a covered work material governed by the terms
395
- of that license document, provided that the further restriction does
396
- not survive such relicensing or conveying.
397
-
398
- If you add terms to a covered work in accord with this section, you
399
- must place, in the relevant source files, a statement of the
400
- additional terms that apply to those files, or a notice indicating
401
- where to find the applicable terms.
402
-
403
- Additional terms, permissive or non-permissive, may be stated in the
404
- form of a separately written license, or stated as exceptions;
405
- the above requirements apply either way.
406
-
407
- 8. Termination.
408
-
409
- You may not propagate or modify a covered work except as expressly
410
- provided under this License. Any attempt otherwise to propagate or
411
- modify it is void, and will automatically terminate your rights under
412
- this License (including any patent licenses granted under the third
413
- paragraph of section 11).
414
-
415
- However, if you cease all violation of this License, then your
416
- license from a particular copyright holder is reinstated (a)
417
- provisionally, unless and until the copyright holder explicitly and
418
- finally terminates your license, and (b) permanently, if the copyright
419
- holder fails to notify you of the violation by some reasonable means
420
- prior to 60 days after the cessation.
421
-
422
- Moreover, your license from a particular copyright holder is
423
- reinstated permanently if the copyright holder notifies you of the
424
- violation by some reasonable means, this is the first time you have
425
- received notice of violation of this License (for any work) from that
426
- copyright holder, and you cure the violation prior to 30 days after
427
- your receipt of the notice.
428
-
429
- Termination of your rights under this section does not terminate the
430
- licenses of parties who have received copies or rights from you under
431
- this License. If your rights have been terminated and not permanently
432
- reinstated, you do not qualify to receive new licenses for the same
433
- material under section 10.
434
-
435
- 9. Acceptance Not Required for Having Copies.
436
-
437
- You are not required to accept this License in order to receive or
438
- run a copy of the Program. Ancillary propagation of a covered work
439
- occurring solely as a consequence of using peer-to-peer transmission
440
- to receive a copy likewise does not require acceptance. However,
441
- nothing other than this License grants you permission to propagate or
442
- modify any covered work. These actions infringe copyright if you do
443
- not accept this License. Therefore, by modifying or propagating a
444
- covered work, you indicate your acceptance of this License to do so.
445
-
446
- 10. Automatic Licensing of Downstream Recipients.
447
-
448
- Each time you convey a covered work, the recipient automatically
449
- receives a license from the original licensors, to run, modify and
450
- propagate that work, subject to this License. You are not responsible
451
- for enforcing compliance by third parties with this License.
452
-
453
- An "entity transaction" is a transaction transferring control of an
454
- organization, or substantially all assets of one, or subdividing an
455
- organization, or merging organizations. If propagation of a covered
456
- work results from an entity transaction, each party to that
457
- transaction who receives a copy of the work also receives whatever
458
- licenses to the work the party's predecessor in interest had or could
459
- give under the previous paragraph, plus a right to possession of the
460
- Corresponding Source of the work from the predecessor in interest, if
461
- the predecessor has it or can get it with reasonable efforts.
462
-
463
- You may not impose any further restrictions on the exercise of the
464
- rights granted or affirmed under this License. For example, you may
465
- not impose a license fee, royalty, or other charge for exercise of
466
- rights granted under this License, and you may not initiate litigation
467
- (including a cross-claim or counterclaim in a lawsuit) alleging that
468
- any patent claim is infringed by making, using, selling, offering for
469
- sale, or importing the Program or any portion of it.
470
-
471
- 11. Patents.
472
-
473
- A "contributor" is a copyright holder who authorizes use under this
474
- License of the Program or a work on which the Program is based. The
475
- work thus licensed is called the contributor's "contributor version".
476
-
477
- A contributor's "essential patent claims" are all patent claims
478
- owned or controlled by the contributor, whether already acquired or
479
- hereafter acquired, that would be infringed by some manner, permitted
480
- by this License, of making, using, or selling its contributor version,
481
- but do not include claims that would be infringed only as a
482
- consequence of further modification of the contributor version. For
483
- purposes of this definition, "control" includes the right to grant
484
- patent sublicenses in a manner consistent with the requirements of
485
- this License.
486
-
487
- Each contributor grants you a non-exclusive, worldwide, royalty-free
488
- patent license under the contributor's essential patent claims, to
489
- make, use, sell, offer for sale, import and otherwise run, modify and
490
- propagate the contents of its contributor version.
491
-
492
- In the following three paragraphs, a "patent license" is any express
493
- agreement or commitment, however denominated, not to enforce a patent
494
- (such as an express permission to practice a patent or covenant not to
495
- sue for patent infringement). To "grant" such a patent license to a
496
- party means to make such an agreement or commitment not to enforce a
497
- patent against the party.
498
-
499
- If you convey a covered work, knowingly relying on a patent license,
500
- and the Corresponding Source of the work is not available for anyone
501
- to copy, free of charge and under the terms of this License, through a
502
- publicly available network server or other readily accessible means,
503
- then you must either (1) cause the Corresponding Source to be so
504
- available, or (2) arrange to deprive yourself of the benefit of the
505
- patent license for this particular work, or (3) arrange, in a manner
506
- consistent with the requirements of this License, to extend the patent
507
- license to downstream recipients. "Knowingly relying" means you have
508
- actual knowledge that, but for the patent license, your conveying the
509
- covered work in a country, or your recipient's use of the covered work
510
- in a country, would infringe one or more identifiable patents in that
511
- country that you have reason to believe are valid.
512
-
513
- If, pursuant to or in connection with a single transaction or
514
- arrangement, you convey, or propagate by procuring conveyance of, a
515
- covered work, and grant a patent license to some of the parties
516
- receiving the covered work authorizing them to use, propagate, modify
517
- or convey a specific copy of the covered work, then the patent license
518
- you grant is automatically extended to all recipients of the covered
519
- work and works based on it.
520
-
521
- A patent license is "discriminatory" if it does not include within
522
- the scope of its coverage, prohibits the exercise of, or is
523
- conditioned on the non-exercise of one or more of the rights that are
524
- specifically granted under this License. You may not convey a covered
525
- work if you are a party to an arrangement with a third party that is
526
- in the business of distributing software, under which you make payment
527
- to the third party based on the extent of your activity of conveying
528
- the work, and under which the third party grants, to any of the
529
- parties who would receive the covered work from you, a discriminatory
530
- patent license (a) in connection with copies of the covered work
531
- conveyed by you (or copies made from those copies), or (b) primarily
532
- for and in connection with specific products or compilations that
533
- contain the covered work, unless you entered into that arrangement,
534
- or that patent license was granted, prior to 28 March 2007.
535
-
536
- Nothing in this License shall be construed as excluding or limiting
537
- any implied license or other defenses to infringement that may
538
- otherwise be available to you under applicable patent law.
539
-
540
- 12. No Surrender of Others' Freedom.
541
-
542
- If conditions are imposed on you (whether by court order, agreement or
543
- otherwise) that contradict the conditions of this License, they do not
544
- excuse you from the conditions of this License. If you cannot convey a
545
- covered work so as to satisfy simultaneously your obligations under this
546
- License and any other pertinent obligations, then as a consequence you may
547
- not convey it at all. For example, if you agree to terms that obligate you
548
- to collect a royalty for further conveying from those to whom you convey
549
- the Program, the only way you could satisfy both those terms and this
550
- License would be to refrain entirely from conveying the Program.
551
-
552
- 13. Use with the GNU Affero General Public License.
553
-
554
- Notwithstanding any other provision of this License, you have
555
- permission to link or combine any covered work with a work licensed
556
- under version 3 of the GNU Affero General Public License into a single
557
- combined work, and to convey the resulting work. The terms of this
558
- License will continue to apply to the part which is the covered work,
559
- but the special requirements of the GNU Affero General Public License,
560
- section 13, concerning interaction through a network will apply to the
561
- combination as such.
562
-
563
- 14. Revised Versions of this License.
564
-
565
- The Free Software Foundation may publish revised and/or new versions of
566
- the GNU General Public License from time to time. Such new versions will
567
- be similar in spirit to the present version, but may differ in detail to
568
- address new problems or concerns.
569
-
570
- Each version is given a distinguishing version number. If the
571
- Program specifies that a certain numbered version of the GNU General
572
- Public License "or any later version" applies to it, you have the
573
- option of following the terms and conditions either of that numbered
574
- version or of any later version published by the Free Software
575
- Foundation. If the Program does not specify a version number of the
576
- GNU General Public License, you may choose any version ever published
577
- by the Free Software Foundation.
578
-
579
- If the Program specifies that a proxy can decide which future
580
- versions of the GNU General Public License can be used, that proxy's
581
- public statement of acceptance of a version permanently authorizes you
582
- to choose that version for the Program.
583
-
584
- Later license versions may give you additional or different
585
- permissions. However, no additional obligations are imposed on any
586
- author or copyright holder as a result of your choosing to follow a
587
- later version.
588
-
589
- 15. Disclaimer of Warranty.
590
-
591
- THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
592
- APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
593
- HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
594
- OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
595
- THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
596
- PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
597
- IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
598
- ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
599
-
600
- 16. Limitation of Liability.
601
-
602
- IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
603
- WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
604
- THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
605
- GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
606
- USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
607
- DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
608
- PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
609
- EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
610
- SUCH DAMAGES.
611
-
612
- 17. Interpretation of Sections 15 and 16.
613
-
614
- If the disclaimer of warranty and limitation of liability provided
615
- above cannot be given local legal effect according to their terms,
616
- reviewing courts shall apply local law that most closely approximates
617
- an absolute waiver of all civil liability in connection with the
618
- Program, unless a warranty or assumption of liability accompanies a
619
- copy of the Program in return for a fee.
620
-
621
- END OF TERMS AND CONDITIONS
622
-
623
- How to Apply These Terms to Your New Programs
624
-
625
- If you develop a new program, and you want it to be of the greatest
626
- possible use to the public, the best way to achieve this is to make it
627
- free software which everyone can redistribute and change under these terms.
628
-
629
- To do so, attach the following notices to the program. It is safest
630
- to attach them to the start of each source file to most effectively
631
- state the exclusion of warranty; and each file should have at least
632
- the "copyright" line and a pointer to where the full notice is found.
633
-
634
- <one line to give the program's name and a brief idea of what it does.>
635
- Copyright (C) <year> <name of author>
636
-
637
- This program is free software: you can redistribute it and/or modify
638
- it under the terms of the GNU General Public License as published by
639
- the Free Software Foundation, either version 3 of the License, or
640
- (at your option) any later version.
641
-
642
- This program is distributed in the hope that it will be useful,
643
- but WITHOUT ANY WARRANTY; without even the implied warranty of
644
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
645
- GNU General Public License for more details.
646
-
647
- You should have received a copy of the GNU General Public License
648
- along with this program. If not, see <http://www.gnu.org/licenses/>.
649
-
650
- Also add information on how to contact you by electronic and paper mail.
651
-
652
- If the program does terminal interaction, make it output a short
653
- notice like this when it starts in an interactive mode:
654
-
655
- <program> Copyright (C) <year> <name of author>
656
- This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
657
- This is free software, and you are welcome to redistribute it
658
- under certain conditions; type `show c' for details.
659
-
660
- The hypothetical commands `show w' and `show c' should show the appropriate
661
- parts of the General Public License. Of course, your program's commands
662
- might be different; for a GUI interface, you would use an "about box".
663
-
664
- You should also get your employer (if you work as a programmer) or school,
665
- if any, to sign a "copyright disclaimer" for the program, if necessary.
666
- For more information on this, and how to apply and follow the GNU GPL, see
667
- <http://www.gnu.org/licenses/>.
668
-
669
- The GNU General Public License does not permit incorporating your program
670
- into proprietary programs. If your program is a subroutine library, you
671
- may consider it more useful to permit linking proprietary applications with
672
- the library. If this is what you want to do, use the GNU Lesser General
673
- Public License instead of this License. But first, please read
674
- <http://www.gnu.org/philosophy/why-not-lgpl.html>.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
readme.txt CHANGED
@@ -1,31 +1,37 @@
1
  === Co-Authors Plus ===
2
- Contributors: batmoo
3
  Donate link: http://digitalize.ca/donate
4
  Tags: authors, users, multiple authors, coauthors, multi-author
5
- Tested up to: 2.8
6
- Requires at least: 2.6
7
- Stable tag: 2.1.1
8
 
9
- Allows multiple authors to be assigned to a Post or Page via search-as-you-type input boxes.
10
 
11
  == Description ==
12
 
13
- Allows multiple authors to be assigned to a Post or Page via the search-as-you-type inputs. Co-authored posts appear on a co-author's posts page and feed. New template tags allow listing of co-authors. Editors and Administrators may assign co-authors to a post. Additionally, co-authors may edit the posts they are associated with, and co-authors who are contributors may only edit posts if they have not been published (as is usual).
14
 
15
- This plugin is an almost complete rewrite of the Co-Authors plugin originally developed at [Shepherd Interactive](http://www.shepherd-interactive.com/ "Shepherd Interactive specializes in web design and development in Portland, Oregon") (2007). The original plugin was inspired by the 'Multiple Authors' plugin by Mark Jaquith (2005).
16
 
17
- The extended version is created by [Mohammad Jangda](http://digitalize.ca), and incorporates search-as-you-type functionality for adding users, which aims to make easy the task of adding multiple users to posts and pages, especially when dealing with a system with hundreds of users (typical of newspaper and magazine sites).
18
-
19
- Version 2.0 is a major re-write of the plugin to utilize the WordPress taxonomy system instead of post meta.
20
 
21
  > *See "Other Notes" section for Template Tags and usage information*
22
 
23
  == Changelog ==
24
 
 
 
 
 
 
 
 
 
25
  = 2009-10-16 / 2.1.1 =
26
 
27
  * Fix for coauthors not being added if their username is different from display name
28
- * Fixes to readme.txt (fixes for textual and puntuation errors, language clarification, minor formatting changes) courtesy of [Waldo Jaquith](http://www.vqronline.org)
29
 
30
  = 2009-10-11 / 2.1 =
31
 
@@ -77,7 +83,7 @@ Version 2.0 is a major re-write of the plugin to utilize the WordPress taxonomy
77
 
78
  = 2009-04-15 / 1.1.1 =
79
 
80
- * Changed SQL query to return only contributer-level and above users.
81
 
82
  = 2009-04-14: 1.1.0 =
83
 
@@ -168,7 +174,7 @@ Note: The $field variable corresponds with the same values accepted by the [the
168
 
169
  * <code>get_coauthors( [$post_id], [$args] )</code>
170
 
171
- This function returns an array of coauthors for the specified post, or if used inside the Loop, the current post active in the Loop. the $args paramater is an array that allows you to specify the order in which the authors should be returned.
172
 
173
  = is coauthor for post =
174
 
1
  === Co-Authors Plus ===
2
+ Contributors: batmoo, danielbachhuber
3
  Donate link: http://digitalize.ca/donate
4
  Tags: authors, users, multiple authors, coauthors, multi-author
5
+ Tested up to: 3.1
6
+ Requires at least: 3.0
7
+ Stable tag: 2.5
8
 
9
+ Allows multiple authors to be assigned to a Posts via search-as-you-type input boxes.
10
 
11
  == Description ==
12
 
13
+ Allows multiple authors to be assigned to a Post, Pages, or Custom Post Types via the search-as-you-type inputs. Co-authored posts appear on a co-author's posts page and feed. New template tags allow listing of co-authors. Editors and Administrators may assign co-authors to a post. Additionally, co-authors may edit the posts they are associated with, and co-authors who are contributors may only edit posts if they have not been published (as is usual).
14
 
15
+ This plugin is an almost complete rewrite of the Co-Authors plugin originally developed at [Shepherd Interactive](http://www.shepherd-interactive.com/) (2007). The original plugin was inspired by the 'Multiple Authors' plugin by Mark Jaquith (2005).
16
 
17
+ The extended version incorporates search-as-you-type functionality for adding users, which aims to make easy the task of adding multiple users to posts and pages, especially when dealing with a system with hundreds of users (typical of newspaper and magazine sites).
 
 
18
 
19
  > *See "Other Notes" section for Template Tags and usage information*
20
 
21
  == Changelog ==
22
 
23
+ = 2011-03-26 / 3.0 =
24
+
25
+ * Custom Post Type Support
26
+ * Compatibility with WP 3.0 and 3.1
27
+ * Gravatars
28
+ * Lots and lots and lots of bug fixes
29
+ * Thanks to everyone who submitted bugs, fixes, and suggestions! And for your patience!
30
+
31
  = 2009-10-16 / 2.1.1 =
32
 
33
  * Fix for coauthors not being added if their username is different from display name
34
+ * Fixes to readme.txt (fixes for textual and punctuation errors, language clarification, minor formatting changes) courtesy of [Waldo Jaquith](http://www.vqronline.org)
35
 
36
  = 2009-10-11 / 2.1 =
37
 
83
 
84
  = 2009-04-15 / 1.1.1 =
85
 
86
+ * Changed SQL query to return only contributor-level and above users.
87
 
88
  = 2009-04-14: 1.1.0 =
89
 
174
 
175
  * <code>get_coauthors( [$post_id], [$args] )</code>
176
 
177
+ This function returns an array of coauthors for the specified post, or if used inside the Loop, the current post active in the Loop. the $args parameter is an array that allows you to specify the order in which the authors should be returned.
178
 
179
  = is coauthor for post =
180
 
template-tags.php CHANGED
@@ -32,214 +32,241 @@ function get_coauthors( $post_id = 0, $args = array() ) {
32
  return $coauthors;
33
  }
34
 
 
 
 
 
 
35
  function is_coauthor_for_post( $user, $post_id = 0 ) {
36
- // @TODO Make this work in or out of loop
37
- $coauthors = get_coauthors($post_id);
38
- if(is_numeric($user)) $user = get_userdata($user)->user_login;
39
- foreach($coauthors as $coauthor) {
40
- if($user == $coauthor->user_login) return true;
 
 
 
 
 
 
 
 
41
  }
42
  return false;
43
  }
44
 
45
- class CoAuthorsIterator {
46
- var $position = -1;
47
- var $original_authordata;
48
- var $authordata_array;
49
- var $count;
50
-
51
- function CoAuthorsIterator($postID = 0){
52
- global $post, $authordata, $wpdb;
53
- $postID = (int)$postID;
54
- if(!$postID && $post)
55
- $postID = (int)$post->ID;
56
- if(!$postID)
57
- trigger_error(__('No post ID provided for CoAuthorsIterator constructor. Are you not in a loop or is $post not set?', 'co-authors-plus')); //return null;
58
-
59
- $this->original_authordata = $authordata;
 
60
  $this->authordata_array = get_coauthors($postID);
61
-
62
- $this->count = count($this->authordata_array);
63
- }
64
-
65
- function iterate(){
66
- global $authordata;
67
- $this->position++;
68
-
69
- //At the end of the loop
70
- if($this->position > $this->count-1){
71
- $authordata = $this->original_authordata;
72
- $this->position = -1;
73
- return false;
74
- }
75
-
76
- //At the beginning of the loop
77
- if($this->position == 0 && !empty($authordata))
78
- $this->original_authordata = $authordata;
79
-
80
- $authordata = $this->authordata_array[$this->position];
81
-
82
- return true;
83
- }
84
-
85
- function get_position(){
86
- if($this->position === -1)
87
- return false;
88
- return $this->position;
89
- }
90
- function is_last(){
91
- return $this->position === $this->count-1;
92
- }
93
- function is_first(){
94
- return $this->position === 0;
95
- }
96
- function count(){
97
- return $this->count;
98
- }
99
- function get_all(){
100
- return $this->authordata_array;
101
- }
102
- }
103
-
104
- //Helper function for the following new template tags
105
- function coauthors__echo($tag, $between, $betweenLast, $before, $after){
106
- $i = new CoAuthorsIterator();
107
- echo $before;
108
- if($i->iterate())
109
- $tag();
110
- while($i->iterate()){
111
- echo $i->is_last() ? $betweenLast : $between;
112
- $tag();
113
- }
114
- echo $after;
115
- }
116
- function coauthors__return($tag){
117
- $return = array();
118
- $i = new CoAuthorsIterator();
119
- if($i->iterate())
120
- $return[] = $tag();
121
- while($i->iterate()){
122
- $return[] = $tag();
123
- }
124
- echo $after;
125
- }
126
-
127
- //Provide co-author equivalents to the existing author template tags
128
- function coauthors($between = null, $betweenLast = null, $before = null, $after = null){
129
- if($between === NULL)
130
- $between = __(COAUTHORS_DEFAULT_BETWEEN, 'co-authors-plus');
131
- if($betweenLast === NULL)
132
- $betweenLast = __(COAUTHORS_DEFAULT_BETWEEN_LAST, 'co-authors-plus');
133
- if($before === NULL)
134
- $before = COAUTHORS_DEFAULT_BEFORE; //__(COAUTHORS_DEFAULT_BEFORE, 'co-authors-plus');
135
- if($after === NULL)
136
- $after = COAUTHORS_DEFAULT_AFTER; //__(COAUTHORS_DEFAULT_AFTER, 'co-authors-plus');
137
- coauthors__echo('the_author', $between, $betweenLast, $before, $after);
138
- }
139
- function coauthors_posts_links($between = null, $betweenLast = null, $before = null, $after = null){
140
- if($between === NULL)
141
- $between = __(COAUTHORS_DEFAULT_BETWEEN, 'co-authors-plus');
142
- if($betweenLast === NULL)
143
- $betweenLast = __(COAUTHORS_DEFAULT_BETWEEN_LAST, 'co-authors-plus');
144
- if($before === NULL)
145
- $before = COAUTHORS_DEFAULT_BEFORE; //__(COAUTHORS_DEFAULT_BEFORE, 'co-authors');
146
- if($after === NULL)
147
- $after = COAUTHORS_DEFAULT_AFTER; //__(COAUTHORS_DEFAULT_AFTER, 'co-authors');
148
- coauthors__echo('the_author_posts_link', $between, $betweenLast, $before, $after);
149
- }
150
- function coauthors_firstnames($between = null, $betweenLast = null, $before = null, $after = null){
151
- if($between === NULL)
152
- $between = __(COAUTHORS_DEFAULT_BETWEEN, 'co-authors-plus');
153
- if($betweenLast === NULL)
154
- $betweenLast = __(COAUTHORS_DEFAULT_BETWEEN_LAST, 'co-authors-plus');
155
- if($before === NULL)
156
- $before = COAUTHORS_DEFAULT_BEFORE; //__(COAUTHORS_DEFAULT_BEFORE, 'co-authors');
157
- if($after === NULL)
158
- $after = COAUTHORS_DEFAULT_AFTER; //__(COAUTHORS_DEFAULT_AFTER, 'co-authors');
159
- coauthors__echo('the_author_firstname', $between, $betweenLast, $before, $after);
160
- }
161
- function coauthors_lastnames($between = null, $betweenLast = null, $before = null, $after = null){
162
- if($between === NULL)
163
- $between = __(COAUTHORS_DEFAULT_BETWEEN, 'co-authors-plus');
164
- if($betweenLast === NULL)
165
- $betweenLast = __(COAUTHORS_DEFAULT_BETWEEN_LAST, 'co-authors-plus');
166
- if($before === NULL)
167
- $before = COAUTHORS_DEFAULT_BEFORE;
168
- if($after === NULL)
169
- $after = COAUTHORS_DEFAULT_AFTER;
170
- coauthors__echo('the_author_lastname', $between, $betweenLast, $before, $after);
171
- }
172
- function coauthors_nicknames($between = null, $betweenLast = null, $before = null, $after = null){
173
- if($between === NULL)
174
- $between = __(COAUTHORS_DEFAULT_BETWEEN, 'co-authors-plus');
175
- if($betweenLast === NULL)
176
- $betweenLast = __(COAUTHORS_DEFAULT_BETWEEN_LAST, 'co-authors-plus');
177
- if($before === NULL)
178
- $before = COAUTHORS_DEFAULT_BEFORE;
179
- if($after === NULL)
180
- $after = COAUTHORS_DEFAULT_AFTER;
181
- coauthors__echo('the_author_nickname', $between, $betweenLast, $before, $after);
182
- }
183
- function coauthors_links($between = null, $betweenLast = null, $before = null, $after = null){
184
- if($between === NULL)
185
- $between = __(COAUTHORS_DEFAULT_BETWEEN, 'co-authors-plus');
186
- if($betweenLast === NULL)
187
- $betweenLast = __(COAUTHORS_DEFAULT_BETWEEN_LAST, 'co-authors-plus');
188
- if($before === NULL)
189
- $before = COAUTHORS_DEFAULT_BEFORE; //__(COAUTHORS_DEFAULT_BEFORE, 'co-authors');
190
- if($after === NULL)
191
- $after = COAUTHORS_DEFAULT_AFTER; //__(COAUTHORS_DEFAULT_AFTER, 'co-authors');
192
- coauthors__echo('the_author_link', $between, $betweenLast, $before, $after);
193
- }
194
- function coauthors_IDs($between = null, $betweenLast = null, $before = null, $after = null){
195
- if($between === NULL)
196
- $between = __(COAUTHORS_DEFAULT_BETWEEN, 'co-authors-plus');
197
- if($betweenLast === NULL)
198
- $betweenLast = __(COAUTHORS_DEFAULT_BETWEEN_LAST, 'co-authors-plus');
199
- if($before === NULL)
200
- $before = COAUTHORS_DEFAULT_BEFORE;
201
- if($after === NULL)
202
- $after = COAUTHORS_DEFAULT_AFTER;
203
- coauthors__echo('the_author_ID', $between, $betweenLast, $before, $after);
204
- }
205
- // @TODO: fix this function
206
- function get_the_coauthor_meta( $field, $user_id = 0 ) {
207
- global $wp_query, $post;
208
 
209
- // inside loop
210
- if($post) {
211
- //use iterator and loop through meta
212
 
213
- } else {
214
- // use regular
215
- if($user_id) {
216
- //$curauth = $wp_query->get_queried_object();
217
-
218
- if(function_exists('get_the_author_meta')) {//get_bloginfo('version') >= 2.8) {
219
- return get_the_author_meta('description', $user_id);
220
- }
221
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
222
  }
223
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
224
 
225
  function the_coauthor_meta( $field, $user_id = 0 ) {
226
- // need before after options
227
  echo get_the_coauthor_meta($field, $user_id);
228
  }
229
 
230
- //customized wp_list_authors() from WP core
231
- /**
232
- * List all the *co-authors* of the blog, with several options available.
233
- * optioncount (boolean) (false): Show the count in parenthesis next to the author's name.
234
- * exclude_admin (boolean) (true): Exclude the 'admin' user that is installed by default.
235
- * show_fullname (boolean) (false): Show their full names.
236
- * hide_empty (boolean) (true): Don't show authors without any posts.
237
- * feed (string) (''): If isn't empty, show links to author's feeds.
238
- * feed_image (string) (''): If isn't empty, use this image to link to feeds.
239
- * echo (boolean) (true): Set to false to return the output, instead of echoing.
240
- * @param array $args The argument array.
241
- * @return null|string The output, if echo is set to false.
 
 
242
  */
 
243
  function coauthors_wp_list_authors($args = '') {
244
  global $wpdb, $coauthors_plus;
245
 
@@ -254,23 +281,11 @@ function coauthors_wp_list_authors($args = '') {
254
  extract($r, EXTR_SKIP);
255
  $return = '';
256
 
257
- // @todo Move select to get_authors()
258
- $authors = $wpdb->get_results("SELECT ID, user_nicename from $wpdb->users " . ($exclude_admin ? "WHERE user_login <> 'admin' " : '') . "ORDER BY display_name");
259
-
260
- $author_count = array();
261
-
262
- $query = "SELECT DISTINCT $wpdb->users.ID AS post_author, $wpdb->terms.name AS user_name, $wpdb->term_taxonomy.count AS count";
263
- $query .= " FROM $wpdb->posts";
264
- $query .= " INNER JOIN $wpdb->term_relationships ON ($wpdb->posts.ID = $wpdb->term_relationships.object_id)";
265
- $query .= " INNER JOIN $wpdb->term_taxonomy ON ($wpdb->term_relationships.term_taxonomy_id = $wpdb->term_taxonomy.term_taxonomy_id)";
266
- $query .= " INNER JOIN $wpdb->terms ON ($wpdb->term_taxonomy.term_id = $wpdb->terms.term_id)";
267
- $query .= " INNER JOIN $wpdb->users ON ($wpdb->terms.name = $wpdb->users.user_login)";
268
- $query .= " WHERE post_type = 'post' AND " . get_private_posts_cap_sql( 'post' );
269
- $query .= " AND $wpdb->term_taxonomy.taxonomy = '$coauthors_plus->coauthor_taxonomy'";
270
- $query .= " GROUP BY post_author";
271
-
272
- foreach ((array) $wpdb->get_results($query) as $row) {
273
- $author_count[$row->post_author] = $row->count;
274
  }
275
 
276
  foreach ( (array) $authors as $author ) {
@@ -278,7 +293,7 @@ function coauthors_wp_list_authors($args = '') {
278
  $link = '';
279
 
280
  $author = get_userdata( $author->ID );
281
- $posts = (isset($author_count[$author->ID])) ? $author_count[$author->ID] : 0;
282
  $name = $author->display_name;
283
 
284
  if ( $show_fullname && ($author->first_name != '' && $author->last_name != '') )
@@ -345,7 +360,4 @@ function coauthors_wp_list_authors($args = '') {
345
  if ( ! $echo )
346
  return $return;
347
  echo $return;
348
- }
349
-
350
-
351
- ?>
32
  return $coauthors;
33
  }
34
 
35
+ /**
36
+ * Checks to see if the the specified user is author of the current global post or post (if specified)
37
+ * @param object|int $user
38
+ * @param int $post_id
39
+ */
40
  function is_coauthor_for_post( $user, $post_id = 0 ) {
41
+ global $post;
42
+
43
+ if( ! $post_id && $post ) $post_id = $post->ID;
44
+ if( ! $post_id ) return false;
45
+
46
+ $coauthors = get_coauthors( $post_id );
47
+ if( is_numeric( $user ) ) {
48
+ $user = get_userdata( $user );
49
+ $user = $user->user_login;
50
+ }
51
+
52
+ foreach( $coauthors as $coauthor ) {
53
+ if( $user == $coauthor->user_login ) return true;
54
  }
55
  return false;
56
  }
57
 
58
+ class CoAuthorsIterator {
59
+ var $position = -1;
60
+ var $original_authordata;
61
+ var $current_author;
62
+ var $authordata_array;
63
+ var $count;
64
+
65
+ function CoAuthorsIterator($postID = 0){
66
+ global $post, $authordata, $wpdb;
67
+ $postID = (int)$postID;
68
+ if(!$postID && $post)
69
+ $postID = (int)$post->ID;
70
+ if(!$postID)
71
+ trigger_error(__('No post ID provided for CoAuthorsIterator constructor. Are you not in a loop or is $post not set?', 'co-authors-plus')); //return null;
72
+
73
+ $this->original_authordata = $this->current_author = $authordata;
74
  $this->authordata_array = get_coauthors($postID);
75
+
76
+ $this->count = count($this->authordata_array);
77
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
78
 
79
+ function iterate(){
80
+ global $authordata;
81
+ $this->position++;
82
 
83
+ //At the end of the loop
84
+ if($this->position > $this->count-1){
85
+ $authordata = $this->current_author = $this->original_authordata;
86
+ $this->position = -1;
87
+ return false;
 
 
 
88
  }
89
+
90
+ //At the beginning of the loop
91
+ if($this->position == 0 && !empty($authordata))
92
+ $this->original_authordata = $authordata;
93
+
94
+ $authordata = $this->current_author = $this->authordata_array[$this->position];
95
+
96
+ return true;
97
+ }
98
+
99
+ function get_position(){
100
+ if($this->position === -1)
101
+ return false;
102
+ return $this->position;
103
+ }
104
+ function is_last(){
105
+ return $this->position === $this->count-1;
106
+ }
107
+ function is_first(){
108
+ return $this->position === 0;
109
+ }
110
+ function count(){
111
+ return $this->count;
112
+ }
113
+ function get_all(){
114
+ return $this->authordata_array;
115
+ }
116
+ }
117
+
118
+ //Helper function for the following new template tags
119
+ function coauthors__echo( $tag, $type = 'tag', $separators = array(), $tag_args = null, $echo = true ) {
120
+ if( ! isset( $separators['between'] ) || $separators['between'] === NULL )
121
+ $separators['between'] = COAUTHORS_DEFAULT_BETWEEN;
122
+ if( ! isset( $separators['betweenLast'] ) || $separators['betweenLast'] === NULL )
123
+ $separators['betweenLast'] = COAUTHORS_DEFAULT_BETWEEN_LAST;
124
+ if( ! isset( $separators['before'] ) || $separators['before'] === NULL )
125
+ $separators['before'] = COAUTHORS_DEFAULT_BEFORE;
126
+ if( ! isset( $separators['after'] ) || $separators['after'] === NULL )
127
+ $separators['after'] = COAUTHORS_DEFAULT_AFTER;
128
+
129
+ $output = '';
130
+
131
+ $i = new CoAuthorsIterator();
132
+ $output .= $separators['before'];
133
+ if( $i->iterate() ) {
134
+ if( $type == 'tag' )
135
+ $output .= $tag( $tag_args );
136
+ elseif( $type == 'field' && isset( $i->current_author->$tag ) )
137
+ $output .= $i->current_author->$tag;
138
+ elseif( $type == 'callback' && is_callable( $tag ) )
139
+ $output .= call_user_func( $tag, $i->current_author );
140
+ }
141
+ while( $i->iterate() ){
142
+ $output .= $i->is_last() ? $separators['betweenLast'] : $separators['between'];
143
+ if( $type == 'tag' )
144
+ $output .= $tag( $tag_args );
145
+ elseif( $type == 'field' && isset( $i->current_author->$tag ) )
146
+ $output .= $i->current_author->$tag;
147
+ elseif( $type == 'callback' && is_callable( $tag ) )
148
+ $output .= call_user_func( $tag, $i->current_author );
149
+ }
150
+ $output .= $separators['after'];
151
+
152
+ if( $echo )
153
+ echo $output;
154
+
155
+ return $output;
156
+ }
157
+
158
+ //Provide co-author equivalents to the existing author template tags
159
+ function coauthors( $between = null, $betweenLast = null, $before = null, $after = null, $echo = true ){
160
+ return coauthors__echo('display_name', 'field', array(
161
+ 'between' => $between,
162
+ 'betweenLast' => $betweenLast,
163
+ 'before' => $before,
164
+ 'after' => $after
165
+ ), null, $echo );
166
+ }
167
+ function coauthors_posts_links( $between = null, $betweenLast = null, $before = null, $after = null, $echo = true ){
168
+ return coauthors__echo('coauthors_posts_links_single', 'callback', array(
169
+ 'between' => $between,
170
+ 'betweenLast' => $betweenLast,
171
+ 'before' => $before,
172
+ 'after' => $after
173
+ ), null, $echo );
174
+ }
175
+ function coauthors_posts_links_single( $author ) {
176
+ return sprintf(
177
+ '<a href="%1$s" title="%2$s">%3$s</a>',
178
+ get_author_posts_url( $author->ID, $author->user_nicename ),
179
+ esc_attr( sprintf( __( 'Posts by %s', 'co-authors-plus' ), get_the_author() ) ),
180
+ get_the_author()
181
+ );
182
+ }
183
+
184
+ function coauthors_firstnames($between = null, $betweenLast = null, $before = null, $after = null, $echo = true ){
185
+ return coauthors__echo('get_the_author_meta', 'tag', array(
186
+ 'between' => $between,
187
+ 'betweenLast' => $betweenLast,
188
+ 'before' => $before,
189
+ 'after' => $after
190
+ ), 'first_name', $echo );
191
+ }
192
+ function coauthors_lastnames($between = null, $betweenLast = null, $before = null, $after = null, $echo = true ) {
193
+ return coauthors__echo('get_the_author_meta', 'tag', array(
194
+ 'between' => $between,
195
+ 'betweenLast' => $betweenLast,
196
+ 'before' => $before,
197
+ 'after' => $after
198
+ ), 'last_name', $echo );
199
+ }
200
+ function coauthors_nicknames($between = null, $betweenLast = null, $before = null, $after = null, $echo = true ) {
201
+ return coauthors__echo('get_the_author_meta', 'tag', array(
202
+ 'between' => $between,
203
+ 'betweenLast' => $betweenLast,
204
+ 'before' => $before,
205
+ 'after' => $after
206
+ ), 'nickname', $echo );
207
+ }
208
+ function coauthors_links($between = null, $betweenLast = null, $before = null, $after = null, $echo = true ) {
209
+ return coauthors__echo('coauthors_links_single', 'callback', array(
210
+ 'between' => $between,
211
+ 'betweenLast' => $betweenLast,
212
+ 'before' => $before,
213
+ 'after' => $after
214
+ ), null, $echo );
215
+ }
216
+ function coauthors_links_single( $author ) {
217
+ if ( get_the_author_meta('url') ) {
218
+ return sprintf( '<a href="%s" title="%s" rel="external">%s</a>',
219
+ get_the_author_meta('url'),
220
+ esc_attr( sprintf(__("Visit %s&#8217;s website"), get_the_author()) ),
221
+ get_the_author()
222
+ );
223
+ } else {
224
+ return get_the_author();
225
  }
226
  }
227
+ function coauthors_IDs($between = null, $betweenLast = null, $before = null, $after = null, $echo = true ) {
228
+ return coauthors__echo('ID', 'field', array(
229
+ 'between' => $between,
230
+ 'betweenLast' => $betweenLast,
231
+ 'before' => $before,
232
+ 'after' => $after
233
+ ), null, $echo );
234
+ }
235
+
236
+ function get_the_coauthor_meta( $field ) {
237
+ global $wp_query, $post;
238
+
239
+ $coauthors = get_coauthors();
240
+ $meta = array();
241
+
242
+ foreach( $coauthors as $coauthor ) {
243
+ $user_id = $coauthor->ID;
244
+ $meta[$user_id] = get_the_author_meta( $field, $user_id );
245
+ }
246
+ return $meta;
247
+ }
248
 
249
  function the_coauthor_meta( $field, $user_id = 0 ) {
250
+ // TODO: need before after options
251
  echo get_the_coauthor_meta($field, $user_id);
252
  }
253
 
254
+ /**
255
+ * List all the *co-authors* of the blog, with several options available.
256
+ * optioncount (boolean) (false): Show the count in parenthesis next to the author's name.
257
+ * exclude_admin (boolean) (true): Exclude the 'admin' user that is installed by default.
258
+ * show_fullname (boolean) (false): Show their full names.
259
+ * hide_empty (boolean) (true): Don't show authors without any posts.
260
+ * feed (string) (''): If isn't empty, show links to author's feeds.
261
+ * feed_image (string) (''): If isn't empty, use this image to link to feeds.
262
+ * echo (boolean) (true): Set to false to return the output, instead of echoing.
263
+ * @param array $args The argument array.
264
+ * @return null|string The output, if echo is set to false.
265
+ *
266
+ * NOTE: This is not perfect and probably won't work that well.
267
+ *
268
  */
269
+
270
  function coauthors_wp_list_authors($args = '') {
271
  global $wpdb, $coauthors_plus;
272
 
281
  extract($r, EXTR_SKIP);
282
  $return = '';
283
 
284
+ $authors = $coauthors_plus->search_authors();
285
+ $author_terms = get_terms( 'author' );
286
+
287
+ foreach ( (array) $author_terms as $author_term ) {
288
+ $author_count[$author_term->slug] = $author_term->count;
 
 
 
 
 
 
 
 
 
 
 
 
289
  }
290
 
291
  foreach ( (array) $authors as $author ) {
293
  $link = '';
294
 
295
  $author = get_userdata( $author->ID );
296
+ $posts = (isset($author_count[$author->user_login])) ? $author_count[$author->user_login] : 0;
297
  $name = $author->display_name;
298
 
299
  if ( $show_fullname && ($author->first_name != '' && $author->last_name != '') )
360
  if ( ! $echo )
361
  return $return;
362
  echo $return;
363
+ }
 
 
 
upgrade.php ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ function coauthors_plus_upgrade( $from ) {
3
+ // TODO: handle upgrade failures
4
+
5
+ if( $from < 2.0 ) coauthors_plus_upgrade_20();
6
+
7
+ // Update to the current global version
8
+ coauthors_plus_update_version(COAUTHORS_PLUS_VERSION);
9
+ }
10
+
11
+ /**
12
+ * Upgrade to 2.0
13
+ * Updates coauthors from old meta-based storage to taxonomy-based
14
+ */
15
+ function coauthors_plus_upgrade_20 () {
16
+ global $coauthors_plus;
17
+
18
+ // Get all posts with meta_key _coauthor
19
+ $all_posts = get_posts(array('numberposts' => '-1'));
20
+
21
+ if(is_array($all_posts)) {
22
+ foreach($all_posts as $single_post) {
23
+
24
+ // reset execution time limit
25
+ set_time_limit( 60 );
26
+
27
+ // create new array
28
+ $coauthors = array();
29
+ // get author id -- try to use get_profile
30
+ $coauthors[] = get_profile_by_id('user_login', (int)$single_post->post_author);
31
+ // get coauthors id
32
+ $legacy_coauthors = get_post_meta($single_post->ID, '_coauthor');
33
+
34
+ if(is_array($legacy_coauthors)) {
35
+ //echo '<p>Has Legacy coauthors';
36
+ foreach($legacy_coauthors as $legacy_coauthor) {
37
+ $legacy_coauthor_login = get_profile_by_id('user_login', (int)$legacy_coauthor);
38
+ if($legacy_coauthor_login) $coauthors[] = $legacy_coauthor_login;
39
+ }
40
+ } else {
41
+ // No Legacy coauthors
42
+ }
43
+ $coauthors_plus->add_coauthors($single_post->ID, $coauthors);
44
+
45
+ }
46
+ }
47
+ coauthors_plus_update_version( '2.0' );
48
+ }
49
+
50
+ function coauthors_plus_update_version( $version ) {
51
+ global $coauthors_plus;
52
+ update_option($co_authors_plus->get_plugin_option_fullname('version'), $version);
53
+ }