Skip to content

Instantly share code, notes, and snippets.

@varunchopra
Last active December 12, 2017 12:52
Show Gist options
  • Save varunchopra/3dbf3585a2f3154b59ea302615d51e10 to your computer and use it in GitHub Desktop.
Save varunchopra/3dbf3585a2f3154b59ea302615d51e10 to your computer and use it in GitHub Desktop.
var maps = {};
function extend(a, b) {
for (var p in b) {
a[p] = b[p];
}
return a;
}
function createFieldMaps(renames, toFns, fromFns) {
var to = extend({}, renames),
from = {};
Object.keys(renames).forEach(function(key) {
from[renames[key]] = key;
});
return {
renames: renames,
to: extend(to, toFns),
from: extend(from, fromFns)
};
}
function mapFields(data, map) {
var field, value, mappedField,
ret = {};
for (field in data) {
value = data[field];
mappedField = map[field];
// no map -> delete
if (!mappedField) {
continue;
// string -> change field name
} else if (typeof mappedField === "string") {
ret[mappedField] = value;
// function -> merge result
} else {
extend(ret, mappedField(value));
}
}
return ret;
}
maps.labels = createFieldMaps({
addNewItem: "add_new_item",
addOrRemoveItems: "add_or_remove_items",
allItems: "all_items",
chooseFromMostUsed: "choose_from_most_used",
editItem: "edit_item",
menuName: "menu_name",
name: "name",
nameAdminBar: "name_admin_bar",
newItemName: "new_item_name",
parentItem: "parent_item",
parentItemColon: "parent_item_colon",
popularItems: "popular_items",
searchItems: "search_items",
separateItemsWithCommas: "separate_items_with_commas",
singularName: "singular_name",
updateItem: "update_item",
viewItem: "view_item"
});
maps.post = createFieldMaps({
author: /* int */ "post_author",
commentStatus: /* string */ "comment_status",
content: /* string */ "post_content",
customFields: /* array */ "custom_fields",
date: /* datetime */ "post_date",
excerpt: /* string */ "post_excerpt",
format: /* string */ "post_format",
id: /* string */ "post_id",
/* readonly */
link: /* string */ "link" /* readonly */ ,
modified: /* datetime */ "post_modified",
menuOrder: /* int */ "menu_order",
name: /* string */ "post_name",
pageTemplate: /* string */ "page_template",
parent: /* int */ "post_parent",
password: /* string */ "post_password",
pingStatus: /* string */ "ping_status",
status: /* string */ "post_status",
sticky: /* bool */ "sticky",
terms: /* struct */ "terms" /* array */ ,
termNames: /* struct */ "terms_names",
thumbnail: /* int */ "post_thumbnail",
title: /* string */ "post_title",
type: /* string */ "post_type"
}, {}, {
post_date_gmt: /* datetime */ function(date) {
return {
date: new Date(date)
};
},
post_modified_gmt: /* datetime */ function(date) {
return {
modified: new Date(date)
};
}
});
maps.postType = createFieldMaps({
_builtin: /* bool */ "_builtin",
cap: /* struct */ "cap",
capabilityType: /* string */ "capability_type",
description: /* string */ "description",
_editLink: /* string */ "_edit_link",
excludeFromSearch: /* bool */ "exclude_from_search",
hasArchive: /* bool */ "has_archive",
hierarchical: /* bool */ "hierarchical",
label: /* string */ "label",
labels: /* struct */ "labels",
mapMetaCap: /* bool */ "map_meta_cap",
menuIcon: /* string */ "menu_icon",
menuPosition: /* int */ "menu_position",
name: /* string */ "name",
"public": /* bool */ "public",
publiclyQuerably: /* bool */ "publicly_queryable",
queryVar: /* mixed */ "query_var",
rewrite: /* mixed */ "rewrite",
showInAdminBar: /* bool */ "show_in_admin_bar",
showInMenu: /* bool */ "show_in_menu",
showInNavMenus: /* bool */ "show_in_nav_menus",
showUi: /* bool */ "show_ui",
supports: /* array */ "supports",
taxonomies: /* array */ "taxonomies"
}, {}, {
cap: function(cap) {
return {
cap: mapFields(cap, maps.postTypeCap.from)
};
},
labels: function(labels) {
return {
labels: mapFields(labels, maps.labels.from)
};
}
});
maps.postTypeCap = createFieldMaps({
deleteOthersPosts: /* string */ "delete_others_posts",
deletePost: /* string */ "delete_post",
deletePosts: /* string */ "delete_posts",
deletePrivatePosts: /* string */ "delete_private_posts",
deletePublishedPosts: /* string */ "delete_published_posts",
editOthersPosts: /* string */ "edit_others_posts",
editPost: /* string */ "edit_post",
editPosts: /* string */ "edit_posts",
editPrivatePosts: /* string */ "edit_private_posts",
editPublishedPosts: /* string */ "edit_published_posts",
publishPosts: /* string */ "publish_posts",
read: /* string */ "read",
readPost: /* sring */ "read_post",
readPrivatePosts: /* string */ "read_private_posts"
});
maps.taxonomy = createFieldMaps({
cap: /* struct */ "cap",
hierarchical: /* bool */ "hierarchical",
name: /* string */ "name",
label: /* string */ "label",
labels: /* struct */ "labels",
objectType: /* array */ "object_type",
"public": /* bool */ "public",
queryVar: /* string */ "query_var",
rewrite: /* struct */ "rewrite",
showInNavMenus: /* bool */ "show_in_nav_menus",
showTagCloud: /* bool */ "show_tagcloud",
showUi: /* bool */ "show_ui"
}, {}, {
cap: function(cap) {
return {
cap: mapFields(cap, maps.taxonomyCap.from)
};
},
labels: function(labels) {
return {
labels: mapFields(labels, maps.labels.from)
};
}
});
maps.taxonomyCap = createFieldMaps({
assignTerms: /* string */ "assign_terms",
deleteTerms: /* string */ "delete_terms",
editTerms: /* string */ "edit_terms",
manageTerms: /* string */ "manage_terms"
});
maps.term = createFieldMaps({
count: /* int */ "count",
/* readonly */
description: /* string */ "description",
name: /* string */ "name",
parent: /* string */ "parent",
slug: /* string */ "slug",
taxonomy: /* string */ "taxonomy",
termId: /* string */ "term_id",
/* readonly */
termTaxonomyId: /* string */ "term_taxonomy_id" /* readonly */
});
maps.file = createFieldMaps({
name: /* string */ "name",
type: /* string */ "type",
bits: /* string */ "bits",
overwrite: /* boolean */ "overwrite",
postId: /* int */ "post_id"
});
maps.media = createFieldMaps({
attachmentId: /* string */ "attachment_id",
/* readonly */
caption: /* string */ "caption",
description: /* string */ "description",
link: /* string */ "link",
parent: /* int */ "parent",
thumbnail: /* string */ "thumbnail",
title: /* string */ "title",
type: /* string */ "type"
}, {}, {
date_created_gmt: /* datetime */ function(date) {
return {
date: new Date(date)
};
},
metadata: /* struct */ function(data) {
return {
metadata: mapFields(data, maps.mediaItemMetadata.from)
};
}
});
maps.mediaItemMetadata = createFieldMaps({
file: /* string */ "file",
height: /* int */ "height",
sizes: /* struct */ "sizes",
width: /* int */ "width"
}, {}, {
sizes: /* struct */ function(size) {
var keys = Object.keys(size),
results = {};
// Loop through the available sizes and map the fields
keys.forEach(function(key, i) {
results[keys[i]] = mapFields(size[keys[i]], maps.mediaItemSize.from);
});
return {
sizes: results
};
},
image_meta: /* struct */ function(data) {
return {
imageMeta: mapFields(data, maps.postThumbnailImageMeta.from)
};
}
});
maps.mediaItemSize = createFieldMaps({
file: /* string */ "file",
height: /* string */ "height",
mimeType: /* string */ "mime-type",
width: /* string */ "width"
});
maps.postThumbnailImageMeta = createFieldMaps({
aperture: /* int */ "aperture",
camera: /* string */ "camera",
caption: /* string */ "caption",
copyright: /* string */ "copyright",
createdTimestamp: /* int */ "created_timestamp",
credit: /* string */ "credit",
focalLength: /* int */ "focal_length",
iso: /* int */ "iso",
keywords: /* array */ "keywords",
orientation: /* string */ "orientation",
shutterSpeed: /* int */ "shutter_speed",
title: /* string */ "title"
});
module.exports = {
to: function(data, type) {
return mapFields(data, maps[type].to);
},
from: function(data, type) {
return mapFields(data, maps[type].from);
},
array: function(data, type) {
var map = maps[type].renames;
return data.map(function(field) {
return map[field];
});
}
};
var url = require("url"),
xmlrpc = require("xmlrpc"),
fieldMap = require("./fields");
// http://codex.wordpress.org/XML-RPC_Support
// http://codex.wordpress.org/XML-RPC_WordPress_API
function extend(a, b) {
for (var p in b) {
a[p] = b[p];
}
return a;
}
function parseArguments(args) {
return [].slice.call(args, 1)
// Remove null arguments
// Null values only exist for optional fields. As of WordPress 4.4,
// null is no longer treated the same as omitting the value. To
// compensate for this, we just drop the argument before calling
// into WordPress. See #25.
.filter(function(value) {
return value !== null;
});
}
function Client(settings) {
["url", "username", "password"].forEach(function(prop) {
if (!settings.hasOwnProperty(prop)) {
throw new Error("Missing required setting: " + prop);
}
});
var parsedUrl = Client.parseUrl(settings.url);
this.rpc = xmlrpc[parsedUrl.secure ? "createSecureClient" : "createClient"]({
host: settings.host || parsedUrl.host,
port: parsedUrl.port,
path: parsedUrl.path,
rejectUnauthorized: settings.rejectUnauthorized !== undefined ? settings.rejectUnauthorized : true,
// Always set Host header in case we're pointing to a different server
// via settings.host
headers: {
Host: parsedUrl.host
},
basic_auth: !settings.basicAuth ? null : {
user: settings.basicAuth.username,
pass: settings.basicAuth.password
}
});
this.blogId = settings.blogId || 0;
this.username = settings.username;
this.password = settings.password;
}
Client.parseUrl = function(wpUrl) {
var urlParts, secure;
// allow URLs without a protocol
if (!(/\w+:\/\//.test(wpUrl))) {
wpUrl = "http://" + wpUrl;
}
urlParts = url.parse(wpUrl);
secure = urlParts.protocol === "https:";
return {
host: urlParts.hostname,
port: urlParts.port || (secure ? 443 : 80),
path: urlParts.path.replace(/\/+$/, "") + "/xmlrpc.php",
secure: secure
};
};
extend(Client.prototype, {
call: function(method) {
var args = parseArguments(arguments),
fn = args.pop();
if (typeof fn !== "function") {
args.push(fn);
fn = null;
}
this.rpc.methodCall(method, args, function(error, data) {
if (!error) {
return fn(null, data);
}
if (error.code === "ENOTFOUND" && error.syscall === "getaddrinfo") {
error.message = "Unable to connect to WordPress.";
} else if (error.message === "Unknown XML-RPC tag 'TITLE'") {
var additional = error.res.statusCode;
if (error.res.statusMessage) {
additional += "; " + error.res.statusMessage;
}
error.message = "(" + additional + ") " + error.message;
}
fn(error);
});
},
authenticatedCall: function() {
var args = [].slice.call(arguments);
args.splice(1, 0, this.blogId, this.username, this.password);
this.call.apply(this, args);
},
listMethods: function(fn) {
this.call("system.listMethods", fn);
}
});
extend(Client.prototype, {
getPost: function(id, fields, fn) {
if (typeof fields === "function") {
fn = fields;
fields = null;
}
if (fields) {
fields = fieldMap.array(fields, "post");
}
this.authenticatedCall("wp.getPost", id, fields, function(error, post) {
if (error) {
return fn(error);
}
fn(null, fieldMap.from(post, "post"));
});
},
getPosts: function(filter, fields, fn) {
if (typeof filter === "function") {
fn = filter;
fields = null;
filter = {};
}
if (typeof fields === "function") {
fn = fields;
fields = null;
}
if (filter.type) {
filter.post_type = filter.type;
delete filter.type;
}
if (filter.status) {
filter.post_status = filter.status;
delete filter.status;
}
if (filter.orderby) {
filter.orderby = fieldMap.array([filter.orderby], "post")[0];
}
if (fields) {
fields = fieldMap.array(fields, "post");
}
this.authenticatedCall("wp.getPosts", filter, fields, function(error, posts) {
if (error) {
return fn(error);
}
fn(null, posts.map(function(post) {
return fieldMap.from(post, "post");
}));
});
},
newPost: function(data, fn) {
this.authenticatedCall("wp.newPost", fieldMap.to(data, "post"), fn);
},
// to remove a term, just set the terms and leave out the id that you want to remove
// to remove a custom field, pass the id with no key or value
editPost: function(id, data, fn) {
this.authenticatedCall("wp.editPost", id, fieldMap.to(data, "post"), fn);
},
deletePost: function(id, fn) {
this.authenticatedCall("wp.deletePost", id, fn);
},
getPostType: function(name, fields, fn) {
if (typeof fields === "function") {
fn = fields;
fields = null;
}
if (fields) {
fields = fieldMap.array(fields, "postType");
}
this.authenticatedCall("wp.getPostType", name, fields, function(error, postType) {
if (error) {
return fn(error);
}
fn(null, fieldMap.from(postType, "postType"));
});
},
getPostTypes: function(filter, fields, fn) {
if (typeof filter === "function") {
fn = filter;
fields = null;
filter = {};
}
if (typeof fields === "function") {
fn = fields;
fields = null;
}
if (Array.isArray(filter)) {
fields = filter;
filter = {};
}
if (fields) {
fields = fieldMap.array(fields, "postType");
}
this.authenticatedCall("wp.getPostTypes", filter, fields, function(error, postTypes) {
if (error) {
return fn(error);
}
Object.keys(postTypes).forEach(function(postType) {
postTypes[postType] = fieldMap.from(postTypes[postType], "postType");
});
fn(null, postTypes);
});
}
});
extend(Client.prototype, {
getTaxonomy: function(name, fn) {
this.authenticatedCall("wp.getTaxonomy", name, function(error, taxonomy) {
if (error) {
return fn(error);
}
fn(null, fieldMap.from(taxonomy, "taxonomy"));
});
},
getTaxonomies: function(fn) {
this.authenticatedCall("wp.getTaxonomies", function(error, taxonomies) {
if (error) {
return fn(error);
}
fn(null, taxonomies.map(function(taxonomy) {
return fieldMap.from(taxonomy, "taxonomy");
}));
});
},
getTerm: function(taxonomy, id, fn) {
this.authenticatedCall("wp.getTerm", taxonomy, id, function(error, term) {
if (error) {
return fn(error);
}
fn(null, fieldMap.from(term, "term"));
});
},
getTerms: function(taxonomy, filter, fn) {
if (typeof filter === "function") {
fn = filter;
filter = {};
}
if (filter.hideEmpty) {
filter.hide_empty = filter.hideEmpty;
delete filter.hideEmpty;
}
if (filter.orderby) {
filter.orderby = fieldMap.array([filter.orderby], "term")[0];
}
this.authenticatedCall("wp.getTerms", taxonomy, filter, function(error, terms) {
if (error) {
return fn(error);
}
fn(null, terms.map(function(term) {
return fieldMap.from(term, "term");
}));
});
},
newTerm: function(data, fn) {
this.authenticatedCall("wp.newTerm", fieldMap.to(data, "term"), fn);
},
editTerm: function(id, data, fn) {
this.authenticatedCall("wp.editTerm", id, fieldMap.to(data, "term"), fn);
},
deleteTerm: function(taxonomy, id, fn) {
this.authenticatedCall("wp.deleteTerm", taxonomy, id, fn);
}
});
extend(Client.prototype, {
getMediaItem: function(id, fn) {
this.authenticatedCall("wp.getMediaItem", id, function(error, media) {
if (error) {
return fn(error);
}
fn(null, fieldMap.from(media, "media"));
});
},
getMediaLibrary: function(filter, fn) {
if (typeof filter === "function") {
fn = filter;
filter = {};
}
this.authenticatedCall("wp.getMediaLibrary", filter, function(error, media) {
if (error) {
return fn(error);
}
fn(null, media.map(function(item) {
return fieldMap.from(item, "media");
}));
});
},
uploadFile: function(data, fn) {
this.authenticatedCall("wp.uploadFile", fieldMap.to(data, "file"), fn);
}
});
module.exports = {
Client: Client,
createClient: function(settings) {
return new Client(settings);
},
fieldMap: fieldMap
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment