diff --git a/admin.php b/admin.php index b56afc3..a878ab0 100644 --- a/admin.php +++ b/admin.php @@ -1,4 +1,7 @@

Settings saved.

"; + } - $messages = array( - 'new_code_empty' => 'Your project code has been cleared. Please enter a new project code to use Optimizely on this site.', - 'new_code_saved' => 'Your project code has been saved. Enjoy using Optimizely!', - 'code_empty' => 'Please enter your project code.' - ); -?> - -
-

- Configuration saved.
'.$messages[$ms]) ?> -

-
- -
-

-
-
-

About Optimizely

-

Simple, fast, and powerful. Optimizely is a dramatically easier way for you to improve your website through A/B testing. Create an experiment in minutes with our easy-to-use visual interface with absolutely no coding or engineering required. Convert your website visitors into customers and earn more revenue today!

-

Register now

-

Create an account at optimizely.com and start A/B testing today! After creating an account you can come back to this configuration page and set up your WordPress website to use Optimizely.

-

Optimizely project code

-

You can find your project code on your project's experiments page. Go to optimizely.com/experiments, make sure you've selected the right project and click on <Project Code>, then click on 'Copy to Clipboard'. You can then paste the code in the box below. Your project code should start with "<script" and end with "</script>".

- - - -

-
-
-
-

".__('Optimizely is almost ready.')." ".sprintf(__('You must enter your Optimizely project code to begin using Optimizely on your site.'), "admin.php?page=optimizely-config")."

"; +

".__('Optimizely is almost ready.')." ".sprintf(__('You must authenticate and choose a project to begin using Optimizely on your site.'), "admin.php?page=optimizely-config")."

"; } add_action('admin_notices', 'optimizely_warning'); return; @@ -81,9 +85,5 @@ function optimizely_warning() { } function optimizely_admin_menu() { - optimizely_load_menu(); -} - -function optimizely_load_menu() { add_submenu_page('plugins.php', __('Optimizely Configuration'), __('Optimizely Configuration'), 'manage_options', 'optimizely-config', 'optimizely_conf'); } diff --git a/config.js b/config.js new file mode 100644 index 0000000..3d4daca --- /dev/null +++ b/config.js @@ -0,0 +1,47 @@ +// Javascript for plugin settings page + +function optimizelyConfigPage() { + var $ = jQuery; + + /* + + AUTHENTICATION W/ OPTIMIZELY + + When the user presses the button, we call the GET projects/ endpoint to list out all the projects in their account. For each project, we show its name in the dropdown and store its ID in the value attribute for submission to a form. + + */ + + $("button#connect_optimizely").click(function(event) { + event.preventDefault(); + $("#project_id").html(""); + + optly = new OptimizelyAPI($("#token").val()); + + optly.get('projects', function(response) { + $("#project_id").empty(); + + $.each(response, function(key, val) { + $("#project_id").append(""); + }); + + $("#project_id").change(); // update project code w/ the default value + }); + + /* + + CHOOSING A PROJECT + + When the user selects a project from the dropdown, we populate the project code box with the Optimizely snippet for that project ID. + + */ + $('#project_id').change(function() { + var id = $('#project_id').val(); + var name = $('#project_id option:selected').text(); + var project_code = ''; + $('#project_code').text(project_code); + $('#project_name').val(name); + }); + + + }); +} \ No newline at end of file diff --git a/config.php b/config.php new file mode 100644 index 0000000..e5e30ca --- /dev/null +++ b/config.php @@ -0,0 +1,48 @@ +
+

+
+
+ +

About Optimizely

+

Simple, fast, and powerful. Optimizely is a dramatically easier way for you to improve your website through A/B testing. Create an experiment in minutes with absolutely no coding or engineering required. Convert your website visitors into customers and earn more revenue: create an account at optimizely.com and start A/B testing today!

+

Optimizely API tokens

+

Once you create an account, you can find your API Token on your account page at optimizely.com/account. +

+ +
+ +

+ + + +

Choose a Project

+ + +

Optimizely will add the following project code to your page automatically:

+ + + +

Variation Code

+

Optimizely will use this variation code to change headlines on your site. We've provided code that works with the default theme, but you might want to add or change it to work with your themes and plugins.

+ + + +

You can use the variables $POST_ID, $OLD_TITLE, and $NEW_TITLE in your code.

+ +

+ + +
+ + + + +
+
\ No newline at end of file diff --git a/edit.js b/edit.js new file mode 100644 index 0000000..8d80e4e --- /dev/null +++ b/edit.js @@ -0,0 +1,193 @@ +function optimizelyEditPage() { + var $ = jQuery; + + // Initialize data from the input fields + var projectId = $('#optimizely_project_id').val(); + var optly = new OptimizelyAPI($("#optimizely_token").val()); + optly.experiment = { + id: $("#optimizely_experiment_id").val(), + status: $("#optimizely_experiment_status").val() + } + + // If there's already an associated experiment, show it in the editor + if (optly.experiment.id) { + showExperiment(optly.experiment); + } else { + $('#optimizely_created').hide(); + } + + // On click, run the createExperiment function + $('#optimizely_create').click(function(){ + createExperiment(); + }); + + // Then, handle starts and pauses on the experiment + $('#optimizely_toggle_running').click(function(){ + if (optly.experiment.status == "Running") { + pauseExperiment(optly.experiment); + } else { + startExperiment(optly.experiment); + } + }); + + // Render the experiment's state on the page + function showExperiment(experiment) { + // ID and links + $("#optimizely_experiment_id").val(experiment.id); + $('#optimizely_view').attr('href','https://www.optimizely.com/edit?experiment_id=' + experiment.id); + $('#optimizely_results').attr('href','https://www.optimizely.com/results?experiment_id=' + experiment.id); + + // Status and buttons + $("#optimizely_experiment_status").val(experiment.status); + $('#optimizely_experiment_status_text').text(experiment.status); + if (experiment.status == "Running") { + $('#optimizely_toggle_running').text('Pause Experiment'); + } else { + $('#optimizely_toggle_running').text('Start Experiment'); + } + + // Hide create button, show status + $('#optimizely_not_created').hide(); + $('#optimizely_created').show(); + + // Update Wordpress backend w/ experiment data + var data = { + action: "update_experiment_meta", + post_id: $('#post_ID').val(), + optimizely_experiment_id: experiment.id, + optimizely_experiment_status: experiment.status + }; + + $('.optimizely_variation').each(function(index, input) { + data[$(input).attr('name')] = $(input).val(); + }); + $.post(wpAjaxUrl, data); + } + + /* + This function creates an experiment by providing a description based on the post's title and an edit_url based on the permalink of the Wordpress post. We send these as a POST request and register a callback to run the onExperimentCreated function when it completes. + */ + function createExperiment() { + $('#optimizely_create').text('Creating...'); + + experiment = {}; + experiment.description = "Wordpress: " + $('#title').val(); + experiment.edit_url = $('#sample-permalink').text(); + + optly.post('projects/' + projectId + '/experiments', experiment, onExperimentCreated); + } + + /* + The experiment we created has two built-in variations, but now we need to add a third and update the content. Since we're adding a variation, we also need to calculate the traffic weight to use for each one. Once we've done this, we'll call the createVariation function explained below. + + Our experiment comes with an Engagement goal, but we'll also add one to measure views to the post. + + */ + function onExperimentCreated(experiment) { + + optly.experiment = experiment; + + var variations = $('.optimizely_variation').filter(function(){return $(this).val().length > 0}) + + // Set variation weights + var numVariations = variations.length + 1; + var variationWeight = Math.floor(10000 / numVariations); + var leftoverWeight = 10000 - variationWeight*numVariations; + + // Create variations + variations.each(function(index, input) { + var weight = variationWeight + (index == 0 ? leftoverWeight : 0); + createVariation(experiment, index + 1, $(input).val(), weight); + }); + + // Create goal + createGoal(experiment); + + } + + /* + We create a pageview goal that measures how many times the post is viewed. We add one url, the permalink, and use the substring match type. We also set "addable" to false so that the goal won't clog up the list of goals for other experiments. + + Finally, we associate the goal with the experiment we just created by adding the experiment's id to experiment_ids. We POST the goal to the projects/{id}/goals endpoint to create it. + */ + function createGoal(experiment) { + + var goal = { + goal_type: 3, // pageview goal + title: "Views to page", + urls: [$('#sample-permalink').text()], + url_match_types: [4], // substring + addable: false, // don't clog up the goal list + experiment_ids: [experiment.id] + } + + optly.post('projects/' + experiment.project_id + '/goals/', goal, checkExperimentReady); + + } + + /* + To create a variation, we first generate the variation code. We use a template based on the Wordpress theme, and then we drop in the values for our variation. The result would be: + + $(".post-27 .entry-title a").text("Alternate Title #1"); + + Once we've generated this variation code, we include it in the js_component parameter of our API request. We also add a variation title and weight. + + In this example, we have two alternate headlines plus an original. When we created the experiment, it also came with two variations that were created automatically. We'll leave variation 0 alone as the original, update variation 1 to use the first alternate headline, and create a new variation 2 with the second alternate headline. + */ + + function createVariation(experiment, index, newTitle, weight) { + + // Generate variation code + var variationTemplate = $('#optimizely_variation_template').val(); + var postId = $('#post_ID').val(); + var originalTitle = $('#title').val(); + var code = variationTemplate + .replace(/\$OLD_TITLE/g, originalTitle) + .replace(/\$NEW_TITLE/g, newTitle) + .replace(/\$POST_ID/g, postId); + + // Request data + var variation = { + "description": newTitle, + "js_component": code, + "weight": weight, + } + + // Update variation #1, create the others + if (index == 1) { + optly.patch('variations/' + experiment.variation_ids[1], variation, checkExperimentReady); + } else { + optly.post('experiments/' + experiment.id + '/variations', variation, checkExperimentReady); + } + + } + + /* + Once all the PUT and POST requests have returned, we're done! At this point, we can let the user know that the experiment is created and ready. + */ + function checkExperimentReady(response) { + if (optly.outstandingRequests == 0) { + showExperiment(optly.experiment); + } + } + + /* + To start a pause an experiment, we just need to change it's status to running. The patch method GETs the experiment metadata, changes the specified fields, and PUTs the object back to Optimizely. + */ + function startExperiment(experiment) { + $('#optimizely_toggle_running').text('Starting...'); + optly.patch('experiments/' + experiment.id, {'status': 'Running'}, function(response) { + optly.experiment = response; + showExperiment(optly.experiment); + }); + } + + function pauseExperiment(experiment) { + $('#optimizely_toggle_running').text('Pausing...'); + optly.patch('experiments/' + experiment.id, {'status': 'Paused'}, function(response) { + optly.experiment = response; + showExperiment(optly.experiment); + }); + } + +} \ No newline at end of file diff --git a/edit.php b/edit.php new file mode 100644 index 0000000..388fdce --- /dev/null +++ b/edit.php @@ -0,0 +1,99 @@ +ID, "post_title$i", true); + $contents .= "

"; + $contents .= ""; + $contents .= ""; + $contents .= "

"; + } + + if ( can_create_experiments() ) { + echo $contents; + + ?> +
+ Create Experiment +
+
+ Start Experiment +

+ View on Optimizely +

Status: ID, 'optimizely_experiment_status', true); ?> +
+ Results: View Results

+
+ + + + + + + + +

Please configure your API credentials in the Optimizely settings page.

+ \ No newline at end of file diff --git a/optimizely.js b/optimizely.js new file mode 100644 index 0000000..708429a --- /dev/null +++ b/optimizely.js @@ -0,0 +1,86 @@ +/* +The OptimizelyAPI class provides a connection to the API via Javascript and lets you make authenticated calls without repeating yourself. + +We store the API token in each instance of the object, and we can connect to multiple different accounts by creating new instances of the OptimizelyAPI class. + +Finally, we keep track of how many requests are outstanding so we can tell when all the calls are complete. +*/ + +OptimizelyAPI = function(token) { + this.outstandingRequests = 0; + this.token = token; +} + +/* +To call the API, we use jQuery's `$.ajax` function, which sends an asynchronous request based on a set of `options`. + +Our function takes four arguments: + +* The request `type`, like GET or POST +* The `endpoint` to hit, like `projects/27` +* The `data` to send along with a POST or PUT request +* A `callback` function to run when the operation is done. The callback should take one argument, the `response`. + +We construct the URL by appending the endpoint to the base API link, and we authenticate by adding the token in the headers section. + +To send data, we set content type to JSON and encode the array as a JSON string to send over the wire. +*/ + +OptimizelyAPI.prototype.call = function(type, endpoint, data, callback) { + + var self = this; + + var options = { + url: "https://www.optimizelyapis.com/experiment/v1/" + endpoint, + type: type, + headers: {"Token": this.token}, + contentType: 'application/json', + success: function(response) { + self.outstandingRequests -= 1; + callback(response); + } + } + + if (data) { + options.data = JSON.stringify(data); + options.dataType = 'json'; + } + + this.outstandingRequests += 1; + jQuery.ajax(options); + +} + +/* +Using our `call` function, we can define convenience functions for GETs, POSTs, PUTs, and DELETEs. +*/ + +OptimizelyAPI.prototype.get = function(endpoint, callback) { + this.call('GET', endpoint, "", callback); +} + +OptimizelyAPI.prototype.delete = function(endpoint, callback) { + this.call('DELETE', endpoint, "", callback); +} + +OptimizelyAPI.prototype.post = function(endpoint, data, callback) { + this.call('POST', endpoint, data, callback); +} + +OptimizelyAPI.prototype.put = function(endpoint, data, callback) { + this.call('PUT', endpoint, data, callback); +} + +/* +We've also added an extra convenience function, `patch`, that updates a model by changing only the specified fields. The function works by reading in an entity, changing a few keys, and then sending it back to Optimizely. +*/ + +OptimizelyAPI.prototype.patch = function(endpoint, data, callback) { + var self = this; + self.get(endpoint, function(base) { + for (var key in data) { + base[key] = data[key]; + } + self.put(endpoint, base, callback); + }); +} \ No newline at end of file diff --git a/optimizely.php b/optimizely.php index 4c3f0f4..7b698c3 100644 --- a/optimizely.php +++ b/optimizely.php @@ -1,14 +1,14 @@ Optimizely is a dramatically easier way for you to improve your website through A/B testing. Create an experiment in minutes with our easy-to-use visual interface with absolutely no coding or engineering required. Convert your website visitors into customers and earn more revenue today! To get started: 1) Click the "Activate" link to the left of this description, 2) Sign up for an Optimizely account, and 3) Go to the settings page, and enter your Optimizely project code. -Author: Arthur Suermondt -Version: 1.0.1 +Author: Arthur Suermondt & Jon Noronha +Version: 2.0.0 Author URI: http://www.optimizely.com/ License: GPL2 */ @@ -30,30 +30,27 @@ */ if ( is_admin() ) require_once dirname( __FILE__ ) . '/admin.php'; + require_once dirname( __FILE__ ) . '/edit.php'; + wp_enqueue_script('jquery'); + wp_enqueue_script('optimizely_api', plugins_url('optimizely.js', __FILE__)); + wp_enqueue_script('optimizely_editor', plugins_url('edit.js', __FILE__)); + wp_localize_script('optimizely_editor', 'wpAjaxUrl', admin_url('admin-ajax.php')); + wp_enqueue_script('optimizely_config', plugins_url('config.js', __FILE__)); + wp_enqueue_style('optimizely_styles', plugins_url('style.css', __FILE__)); -// forcing Optimizely to load first in the head tag + +$DEFAULT_VARIATION_TEMPLATE = '$(".post-$POST_ID .entry-title a").text("$NEW_TITLE");'; +add_option('optimizely_variation_template', $DEFAULT_VARIATION_TEMPLATE); + +// Force Optimizely to load first in the head tag add_action('wp_head', 'add_optimizely_script', -1000); function add_optimizely_script() { - if ( empty( $project_code) ) { - $project_code = get_option('optimizely_project_code'); - if ( !empty($project_code)) { - - $project_code_html = html_entity_decode($project_code); - $patterns = array('/\'; - - } - } + echo get_option('optimizely_project_code'); +} +function can_create_experiments() { + return get_option('optimizely_token'); } ?> \ No newline at end of file diff --git a/readme.txt b/readme.txt index 07a381e..d40219d 100644 --- a/readme.txt +++ b/readme.txt @@ -1,9 +1,9 @@ === Optimizely === -Contributors: arthuracs +Contributors: arthuracs, jonslaught Tags: optimizely, ab testing, split testing, website optimization Requires at least: 3.0 -Tested up to: 3.4 -Stable tag: 1.0.1 +Tested up to: 3.9 +Stable tag: 2.0.0 License: GPLv2 or later License URI: http://www.gnu.org/licenses/gpl-2.0.html @@ -23,12 +23,15 @@ Sign up at [Optimizely.com](http://www.optimizely.com). 1. Upload the Optimizely WordPress plugin to your blog 2. Activate the plugin through the 'Plugins' menu in WordPress -3. Enter your Optimizely project code in the plugin's settings page. +3. Enter your Optimizely API token in the plugin's settings page, choose a project to use, then save. You're ready to start using Optimizely! == Changelog == += 2.0.0 = +* Added headline testing + = 1.0.1 = * Prioritizing the Optimizely code snippet so that it appears above other scripts. diff --git a/style.css b/style.css new file mode 100644 index 0000000..2390918 --- /dev/null +++ b/style.css @@ -0,0 +1,5 @@ +.code { + width: 100%; + font-family: Consolas, Monaco, "Courier New", Courier, monospace; + font-size: 1.5em; +} \ No newline at end of file