From 80845d7ffc2e1675c8777be3e192fee4ea5d5849 Mon Sep 17 00:00:00 2001
From: Ron Shimshock <ron@shimshockgroup.com>
Date: Tue, 26 Apr 2016 08:04:56 -0500
Subject: [PATCH] Preset hosting, sharing, mute, validation

---
 js/jw_player.admin.js                            |  49 +++
 jw_player.admin.inc                              | 120 +++++-
 jw_player.module                                 |  37 ++
 plugins/export_ui/jw_player_ctools_export_ui.inc | 444 +++++++++++++++--------
 theme/jw_player.theme.inc                        |  45 +++
 5 files changed, 549 insertions(+), 146 deletions(-)
 create mode 100644 js/jw_player.admin.js
 create mode 100644 theme/jw_player.theme.inc

diff --git a/js/jw_player.admin.js b/js/jw_player.admin.js
new file mode 100644
index 0000000..c7ffba7
--- /dev/null
+++ b/js/jw_player.admin.js
@@ -0,0 +1,49 @@
+(function ($) {
+
+  Drupal.behaviors.jw_player_admin = {
+    attach: function (context, settings) {
+      var resp = '#edit-settings-responsive';
+      var width_suffix = '.form-item-settings-width .field-suffix';
+
+      if ($(resp).is(':checked')) {
+        $(width_suffix).text('%');
+      }
+
+      $(resp).change(function () {
+        if ($(this).is(':checked')) {
+          $(width_suffix).text('%');
+        }
+        else {
+          $(width_suffix).text('pixels');
+        }
+      });
+    }
+  };
+
+  Drupal.behaviors.jwplayerSharingSites = {
+    attach: function (context, settings) {
+      $('#edit-settings-sharing-sites-show input.form-checkbox', context).once('settings-sharing-sites', function () {
+        var $checkbox = $(this);
+        // Retrieve the tabledrag row belonging to this content.
+        var $row = $('#' + $checkbox.attr('id').replace(/-show-/, '-order-') + '-weight', context).closest('tr');
+        
+        // Bind click handler to this checkbox to conditionally show and hide the
+        // filter's tableDrag row.
+        $checkbox.bind('click.sharingSitesUpdate', function () {
+          if ($checkbox.is(':checked')) {
+            $row.show();
+          }
+          else {
+            $row.hide();
+          }
+          // Restripe table after toggling visibility of table row.
+          Drupal.tableDrag['jw-player-sharing-sites-order'].restripeTable();
+        });
+
+        // Trigger our bound click handler to update elements to initial state.
+        $checkbox.triggerHandler('click.sharingSitesUpdate');
+      });
+    }
+  };
+
+})(jQuery);
diff --git a/jw_player.admin.inc b/jw_player.admin.inc
index e65aeb8..ed1d989 100644
--- a/jw_player.admin.inc
+++ b/jw_player.admin.inc
@@ -25,6 +25,28 @@ function jw_player_settings_form($form) {
     '#description' => t('Select the version of JWPlayer you are using.'),
   );
 
+  $form['jw_player_hosting'] = array(
+    '#type' => 'radios',
+    '#title' => t('Hosting type'),
+    '#options' => array(
+      'self' => t('Self-Hosted'),
+      'cloud' => t('Cloud-Hosted'),
+    ),
+    '#default_value' => variable_get('jw_player_hosting', 'self'),
+    '#description' => t('Choose if JW Player will be downloaded and self-hosted, or site will use JW Player\'s cloud-hosting service.'),
+    '#states' => array(
+      'unchecked' => array(
+        'select[name="jw_player_version"]' => array('value' => '5'),
+      ),
+      'visible' => array(
+        array(
+          array('select[name="jw_player_version"]' => array('value' => '6')),
+          array('select[name="jw_player_version"]' => array('value' => '7')),
+        ),
+      ),
+    ),
+  );
+
   $form['jw_player_key'] = array(
     '#type' => 'textfield',
     '#title' => t('Self-Hosted Player License Key (version 6.x)'),
@@ -33,9 +55,23 @@ function jw_player_settings_form($form) {
     )),
     '#default_value' => variable_get('jw_player_key', NULL),
     '#states' => array(
+      'required' => array(
+        array(
+          'select[name="jw_player_version"]' => array('value' => '5'),
+        ),
+        array(
+          'select[name="jw_player_version"]' => array('value' => '6'),
+          ':input[name="jw_player_hosting"]' => array('value' => 'self'),
+        ),
+      ),
       'visible' => array(
-        array('select[name="jw_player_version"]' => array('value' => '5')),
-        array('select[name="jw_player_version"]' => array('value' => '6')),
+        array(
+          'select[name="jw_player_version"]' => array('value' => '5'),
+        ),
+        array(
+          'select[name="jw_player_version"]' => array('value' => '6'),
+          ':input[name="jw_player_hosting"]' => array('value' => 'self'),
+        ),
       ),
     ),
   );
@@ -48,8 +84,13 @@ function jw_player_settings_form($form) {
     )),
     '#default_value' => variable_get('jw_player_key_7', NULL),
     '#states' => array(
+      'required' => array(
+        'select[name="jw_player_version"]' => array('value' => '7'),
+        ':input[name="jw_player_hosting"]' => array('value' => 'self'),
+      ),
       'visible' => array(
         'select[name="jw_player_version"]' => array('value' => '7'),
+        ':input[name="jw_player_hosting"]' => array('value' => 'self'),
       ),
     ),
   );
@@ -61,7 +102,82 @@ function jw_player_settings_form($form) {
       '@url' => $url,
     )),
     '#default_value' => variable_get('jw_player_cloud_player_default', NULL),
+    '#states' => array(
+      'required' => array(
+        array(
+          'select[name="jw_player_version"]' => array('value' => '6'),
+          ':input[name="jw_player_hosting"]' => array('value' => 'cloud'),
+        ),
+        array(
+          'select[name="jw_player_version"]' => array('value' => '7'),
+          ':input[name="jw_player_hosting"]' => array('value' => 'cloud'),
+        ),
+      ),
+      'visible' => array(
+        array(
+          'select[name="jw_player_version"]' => array('value' => '6'),
+          ':input[name="jw_player_hosting"]' => array('value' => 'cloud'),
+        ),
+        array(
+          'select[name="jw_player_version"]' => array('value' => '7'),
+          ':input[name="jw_player_hosting"]' => array('value' => 'cloud'),
+        ),
+      ),
+    ),
+  );
+
+  // There are four possible JW Player embedding methods:
+  // https://support.jwplayer.com/customer/portal/articles/1406723#fndtn-method-1
+  // Including a "preset source" allows for local Drupal presets that override
+  // cloud-hosted player settings in JW Player 7.
+  $form['jw_player_preset_source'] = array(
+    '#type' => 'radios',
+    '#title' => t('Preset source'),
+    '#options' => array(
+      'drupal' => t('Use Drupal-defined presets'),
+      'jwplayer' => t('Use presets defined on JWPlayer.com'),
+    ),
+    '#default_value' => variable_get('jw_player_preset_source', 'drupal'),
+    '#description' => t('When creating JW Player presets, select if the source should be defined on Drupal, or by using definitions set within your JWPlayer.com account.'),
+    '#states' => array(
+      'visible' => array(
+        array(
+          'select[name="jw_player_version"]' => array('value' => '7'),
+          ':input[name="jw_player_hosting"]' => array('value' => 'cloud'),
+        ),
+      ),
+    ),
   );
 
+  $form['#validate'][] = 'jw_player_settings_form_validate';
+
   return system_settings_form($form);
 }
+
+/**
+ * General settings form validate
+ */
+function jw_player_settings_form_validate($form, &$form_state) {
+  $values = $form_state['values'];
+  $v = (int) $values['jw_player_version'];
+  $host = $values['jw_player_hosting'];
+  if (($v == 6 || $v == 7) && $host == 'cloud' && empty($values['jw_player_cloud_player_default'])) {
+    form_set_error('jw_player_cloud_player_default', t('Default Cloud Player Url is required when Hosting type is set to "Cloud-Hosted".'));
+  }
+  else if ($v == 7 && $host == 'cloud' && !empty($values['jw_player_cloud_player_default'])) {
+    preg_match(jw_player_library_url_regex(), $values['jw_player_cloud_player_default'], $matches);
+    if (!isset($matches[2])) {
+      form_set_error('jw_player_cloud_player_default', t('Default Cloud Player Url does not match format provided by JWPlayer.com.'));
+    }
+  }
+  else if ($v == 7 && $host == 'self' && empty($values['jw_player_key_7'])) {
+    form_set_error('jw_player_key_7', t('Self-Hosted Player License Key is required when Hosting type is set to "Self-Hosted".'));
+  }
+  else if (($v == 5 || $v == 6) && $host == 'self' && empty($values['jw_player_key'])) {
+    form_set_error('jw_player_key', t('Self-Hosted Player License Key is required when Hosting type is set to "Self-Hosted".'));
+  }
+}
+
+function jw_player_library_url_regex() {
+  return '/^http(s?):\/\/content\.jwplatform\.com\/libraries\/(.*).js$/i';
+}
diff --git a/jw_player.module b/jw_player.module
index dc43d23..4fec063 100644
--- a/jw_player.module
+++ b/jw_player.module
@@ -55,6 +55,10 @@ function jw_player_theme() {
       ),
       'template' => 'theme/jw_player',
     ),
+    'jw_player_sharing_sites_order' => array(
+      'render element' => 'element',
+      'file' => 'theme/jw_player.theme.inc',
+    ),
   );
 }
 
@@ -693,6 +697,19 @@ function jw_player_library() {
       }
     }
   }
+  
+  $libraries['jw_player_admin'] = array(
+    'title' => t('jQuery support in JW Player admin pages.'),
+    'version' => VERSION,
+    'js' => array(
+      drupal_get_path('module', 'jw_player') . '/js/jw_player.admin.js' => array(
+        'type' => 'file',
+        'weight' => -9,
+        'group' => JS_DEFAULT,
+        'scope' => 'footer',
+      ),
+    ),
+  );
   return $libraries;
 }
 
@@ -809,6 +826,26 @@ function jw_player_use_legacy() {
 }
 
 /**
+ * Checks if using local presets (either self-hosted, or
+ * cloud-hosted with presets controlled by Drupal).
+ *
+ * @return bool
+ *   TRUE if local presets are used, FALSE otherwise.
+ */
+function jw_player_use_local_presets() {
+  $host = variable_get('jw_player_hosting', 'self');
+  $source = variable_get('jw_player_preset_source', 'drupal');
+  
+  if ($host == 'self' || ($host == 'cloud' && $source == 'drupal')) {
+    $local_presets = TRUE;
+  }
+  else {
+    $local_presets = FALSE;
+  }
+  return (!jw_player_use_legacy() && $local_presets);
+}
+
+/**
  * Gets the correct key for the corresponding JW Player version.
  *
  * @return string
diff --git a/plugins/export_ui/jw_player_ctools_export_ui.inc b/plugins/export_ui/jw_player_ctools_export_ui.inc
index e929d32..e264619 100644
--- a/plugins/export_ui/jw_player_ctools_export_ui.inc
+++ b/plugins/export_ui/jw_player_ctools_export_ui.inc
@@ -115,174 +115,330 @@ function jw_player_ctools_export_ui_form(&$form, &$form_state) {
     );
   }
 
-  $form['settings']['responsive'] = array(
-    '#type' => 'checkbox',
-    '#title' => 'Use a Responsive Design',
-    '#description' => 'Enable Responsive Design using a percentage based width and an aspect ratio.',
-    '#default_value' => $settings['responsive'],
-    '#weight' => 4.99,
-  );
+  // Use preset form if legacy or a version that is configured with local presets.
+  if (jw_player_use_legacy() || jw_player_use_local_presets()) {
+    $form['settings']['responsive'] = array(
+      '#type' => 'checkbox',
+      '#title' => 'Use a Responsive Design',
+      '#description' => t('Enable Responsive Design using a percentage based width and an aspect ratio.'),
+      '#default_value' => $settings['responsive'],
+      '#weight' => 4.99,
+    );
 
-  $form['settings']['width'] = array(
-    '#type' => 'textfield',
-    '#size' => 10,
-    '#title' => t('Width'),
-    '#description' => t('Enter the width for this player.'),
-    '#field_suffix' => ' ' . t('pixels or percentage'),
-    '#default_value' => $settings['width'],
-    '#required' => TRUE,
-    '#weight' => 5,
-  );
+    $form['settings']['width'] = array(
+      '#type' => 'textfield',
+      '#size' => 10,
+      '#title' => t('Width'),
+      '#description' => t('Enter the width for this player.'),
+      '#field_suffix' => t('pixels'),
+      '#default_value' => $settings['width'],
+      '#required' => TRUE,
+      '#weight' => 5,
+    );
 
-  $form['settings']['height'] = array(
-    '#type' => 'textfield',
-    '#size' => 10,
-    '#title' => t('Height'),
-    '#description' => t('Enter the height for this player.'),
-    '#field_suffix' => ' ' . t('pixels'),
-    '#default_value' => $settings['height'],
-    '#required' => TRUE,
-    '#weight' => 6,
-    '#states' => array(
-      'visible' => array(
-        ':input[name="settings[responsive]"]' => array('checked' => FALSE),
+    $form['settings']['height'] = array(
+      '#type' => 'textfield',
+      '#size' => 10,
+      '#title' => t('Height'),
+      '#description' => t('Enter the height for this player.'),
+      '#field_suffix' => t('pixels'),
+      '#default_value' => $settings['height'],
+      '#weight' => 6,
+      '#states' => array(
+        'required' => array(
+          ':input[name="settings[responsive]"]' => array('checked' => FALSE),
+        ),
+        'visible' => array(
+          ':input[name="settings[responsive]"]' => array('checked' => FALSE),
+        ),
       ),
-    ),
-  );
+    );
 
-  $form['settings']['aspectratio'] = array(
-    '#type' => 'textfield',
-    '#size' => 10,
-    '#title' => t('Aspect ratio'),
-    '#description' => t('Enter the aspect ratio for this player.'),
-    '#default_value' => isset($settings['aspectratio']) ? $settings['aspectratio'] : '16:9',
-    '#required' => TRUE,
-    '#weight' => 6,
-    '#states' => array(
-      'visible' => array(
-        ':input[name="settings[responsive]"]' => array('checked' => TRUE),
+    $form['settings']['aspectratio'] = array(
+      '#type' => 'textfield',
+      '#size' => 10,
+      '#title' => t('Aspect ratio'),
+      '#description' => t('Enter the aspect ratio for this player.'),
+      '#default_value' => isset($settings['aspectratio']) ? $settings['aspectratio'] : '16:9',
+      '#weight' => 6,
+      '#states' => array(
+        'required' => array(
+          ':input[name="settings[responsive]"]' => array('checked' => TRUE),
+        ),
+        'visible' => array(
+          ':input[name="settings[responsive]"]' => array('checked' => TRUE),
+        ),
       ),
-    ),
-  );
+    );
 
-  $form['settings']['controls'] = array(
-    '#title' => t('Controls'),
-    '#type' => 'checkbox',
-    '#description' => t('Whether to display the video controls (controlbar, icons and dock).'),
-    '#default_value' => !empty($settings['controls']) ? $settings['controls']: TRUE,
-    '#weight' => 7,
-  );
+    $form['settings']['controls'] = array(
+      '#title' => t('Controls'),
+      '#type' => 'checkbox',
+      '#description' => t('Whether to display the video controls (controlbar, icons and dock).'),
+      '#default_value' => !empty($settings['controls']) ? $settings['controls']: TRUE,
+      '#weight' => 7,
+    );
 
-  // Playlist settings
-  // @todo Playlist support was removed during refactoring and is supported
-  // differently using the HTML5 version of JW Player. Support needs to be
-  // brought back.
-
-  // $form['settings']['playlist.position'] = array(
-  //   '#title' => t('Playlist Position'),
-  //   '#type' => 'select',
-  //   '#description' => t('The positon of the playlist'),
-  //   '#default_value' => !empty($settings['playlist.position']) ? $settings['playlist.position']: 'right',
-  //   '#options' => array(
-  //     'none' => t('None'),
-  //     'bottom' => t('Bottom'),
-  //     'top' => t('Top'),
-  //     'right' => t('Right'),
-  //     'left' => t('Left'),
-  //     'over' => t('Over')
-  //   ),
-  //   '#weight' => 8,
-  // );
-  //
-  //  $form['settings']['playlist.size'] = array(
-  //   '#title' => t('Playlist Width'),
-  //   '#type' => 'textfield',
-  //   '#description' => t('The playlist width in pixels.'),
-  //   '#default_value' => !empty($settings['playlist.size']) ? $settings['playlist.size']: '180',
-  //   '#field_suffix' => ' ' . t('pixels'),
-  //   '#size' => 10,
-  //   '#weight' => 9,
-  // );
-  
-  $form['settings']['stretching'] = array(
-    '#title' => t('Stretching'),
-    '#description' => t('How to resize the poster and video to fit the display.'),
-    '#type' => 'select',
-    '#default_value' => !empty($settings['stretching']) ? $settings['stretching']: 'uniform',
-    '#options' => array(
-      'none' => t('None (keep original dimensions)'),
-      'exactfit' => t(' Exact Fit (stretch disproportionally)'),
-      'uniform' => t('Uniform (stretch proportionally; black borders)'),
-      'fill' => t('Fill (stretch proportionally; parts cut off)'),
-      ),
-  );
+    // Playlist settings
+    if (jw_player_use_legacy()) {
+      $form['settings']['playlist_position'] = array(
+        '#title' => t('Playlist position'),
+        '#type' => 'select',
+        '#description' => t('The position of the playlist'),
+        '#default_value' => !empty($settings['playlist_position']) ? $settings['playlist_position'] : 'right',
+        '#options' => array(
+          'none' => t('None'),
+          'bottom' => t('Bottom'),
+          'top' => t('Top'),
+          'right' => t('Right'),
+          'left' => t('Left'),
+          'over' => t('Over')
+        ),
+        '#weight' => 16,
+      );
 
-  // Add preset plugin settings.
-  foreach (jw_player_preset_plugins() as $plugin => $info) {
-    $form['settings']['plugins']['#weight'] = 8;
-
-    // Fieldset per plugin.
-    $form['settings']['plugins'][$plugin] = array(
-      '#type' => 'fieldset',
-      '#title' => check_plain($info['name']),
-      '#description' => check_plain($info['description']),
-      '#tree' => TRUE,
-      '#weight' => 10,
-      '#collapsible' => TRUE,
-      '#collapsed' => FALSE,
-    );
+      $form['settings']['playlist_size'] = array(
+        '#title' => t('Playlist width'),
+        '#type' => 'textfield',
+        '#description' => t('The playlist width in pixels.'),
+        '#default_value' => !empty($settings['playlist_size']) ? $settings['playlist_size'] : '180',
+        '#field_suffix' => t('pixels'),
+        '#size' => 10,
+        '#weight' => 17,
+      );
+    }
 
-    // Enable/disable plugin setting.
-    $form['settings']['plugins'][$plugin]['enable'] = array(
-      '#type' => 'checkbox',
-      '#title' => t('Enable'),
-      '#description' => check_plain($info['description']),
-      '#default_value' => isset($settings['plugins'][$plugin]['enable']) ? $settings['plugins'][$plugin]['enable'] : FALSE,
+    $form['settings']['stretching'] = array(
+      '#title' => t('Stretching'),
+      '#description' => t('How to resize the poster and video to fit the display.'),
+      '#type' => 'select',
+      '#default_value' => !empty($settings['stretching']) ? $settings['stretching'] : 'uniform',
+      '#options' => array(
+        'none' => t('None (keep original dimensions)'),
+        'exactfit' => t('Exact Fit (stretch disproportionally)'),
+        'uniform' => t('Uniform (stretch proportionally; black borders)'),
+        'fill' => t('Fill (stretch proportionally; parts cut off)'),
+      ),
+      '#weight' => 2,
     );
 
-    // Add each config option specified in the plugin. Config options should be
-    // in FAPI structure.
-    if (is_array($info['config options']) and !empty($info['config options'])) {
-      foreach ($info['config options'] as $option => $element) {
-        // Note: Each config option must be a complete FAPI element, except for
-        // the #title which is optional. If the #title is not provided, we use
-        // the name of the config option as the title.
-        if (!isset($element['#title'])) {
-          $element['#title'] = drupal_ucfirst($option);
+    // Add preset plugin settings.
+    foreach (jw_player_preset_plugins() as $plugin => $info) {
+      $form['settings']['plugins']['#weight'] = 8;
+
+      // Fieldset per plugin.
+      $form['settings']['plugins'][$plugin] = array(
+        '#type' => 'fieldset',
+        '#title' => check_plain($info['name']),
+        '#description' => check_plain($info['description']),
+        '#tree' => TRUE,
+        '#weight' => 10,
+        '#collapsible' => TRUE,
+        '#collapsed' => FALSE,
+      );
+
+      // Enable/disable plugin setting.
+      $form['settings']['plugins'][$plugin]['enable'] = array(
+        '#type' => 'checkbox',
+        '#title' => t('Enable'),
+        '#description' => check_plain($info['description']),
+        '#default_value' => isset($settings['plugins'][$plugin]['enable']) ? $settings['plugins'][$plugin]['enable'] : FALSE,
+      );
+
+      // Add each config option specified in the plugin. Config options should be
+      // in FAPI structure.
+      if (is_array($info['config options']) and !empty($info['config options'])) {
+        foreach ($info['config options'] as $option => $element) {
+          // Note: Each config option must be a complete FAPI element, except for
+          // the #title which is optional. If the #title is not provided, we use
+          // the name of the config option as the title.
+          if (!isset($element['#title'])) {
+            $element['#title'] = drupal_ucfirst($option);
+          }
+          // Alter the default value if a setting has been saved previously.
+          $element['#default_value'] = !empty($settings['plugins'][$plugin][$option]) ? $settings['plugins'][$plugin][$option] : $element['#default_value'];
+          // Make the whole element visible only if the plugin is checked (enabled).
+          $element['#states'] = array(
+            'visible' => array(
+              'input[name="settings[plugins][' . $plugin . '][enable]"]' => array('checked' => TRUE),
+            ),
+          );
+          // Add the element to the FAPI structure.
+          $form['settings']['plugins'][$plugin][$option] = $element;
         }
-        // Alter the default value if a setting has been saved previously.
-        $element['#default_value'] = !empty($settings['plugins'][$plugin][$option]) ? $settings['plugins'][$plugin][$option] : $element['#default_value'];
-        // Make the whole element visible only if the plugin is checked (enabled).
-        $element['#states'] = array(
+      }
+    }
+
+    // Social media sharing.
+    if (!jw_player_use_legacy()) {
+      $form['settings']['sharing'] = array(
+        '#type' => 'checkbox',
+        '#title' => t('Sharing'),
+        '#description' => t('Enable the social sharing overlay. If no sharing options are selected, the page URL link with default sharing sites will be shown.'),
+        '#default_value' => !empty($settings['sharing']) ? $settings['sharing'] : FALSE,
+        '#weight' => 11,
+      );
+
+      $form['settings']['sharing_heading'] = array(
+        '#type' => 'textfield',
+        '#title' => t('Sharing heading text'),
+        '#description' => t('Short, instructive text to display at the top of the sharing screen.'),
+        '#default_value' => !empty($settings['sharing_heading']) ? $settings['sharing_heading'] : t('Share Video'),
+        '#states' => array(
           'visible' => array(
-               'input[name="settings[plugins][' . $plugin . '][enable]"]' => array('checked' => TRUE),
-              ),
+            ':input[name="settings[sharing]"]' => array('checked' => TRUE),
+          ),
+        ),
+        '#weight' => 12,
+      );
+
+      $sharing_sites = array(
+        'facebook' => t('Facebook'),
+        'twitter' => t('Twitter'),
+        'pinterest' => t('Pinterest'),
+        'email' => t('Email'),
+        'tumblr' => t('Tumblr'),
+        'googleplus' => t('Google Plus'),
+        'reddit' => t('Reddit'),
+        'linkedin' => t('LinkedIn'),
+      );
+      $form['settings']['sharing_sites_show'] = array(
+        '#type' => 'checkboxes',
+        '#title' => t('Sharing sites'),
+        '#options' => $sharing_sites,
+        '#description' => t('Built-in social network options to include with the sharing overlay.'),
+        '#default_value' => !empty($settings['sharing_sites_show']) ? $settings['sharing_sites_show'] : array(),
+        '#states' => array(
+          'visible' => array(
+            ':input[name="settings[sharing]"]' => array('checked' => TRUE),
+          ),
+        ),
+        '#weight' => 13,
+      );
+
+      // Filter sharing sites order (tabledrag).
+      $form['settings']['sharing_sites_order'] = array(
+        '#type' => 'item',
+        '#title' => t('Sharing sites order'),
+        '#theme' => 'jw_player_sharing_sites_order',
+        '#states' => array(
+          'visible' => array(
+            ':input[name="settings[sharing]"]' => array('checked' => TRUE),
+          ),
+        ),
+        '#weight' => 14,
+      );
+
+      $counter = -50;
+      $sharing_sites_order_defaults = array();
+      foreach ($sharing_sites as $site_id => $sharing_site) {
+        $sharing_sites_order_defaults[$site_id] = $counter;
+        $counter++;
+      }
+      foreach ($sharing_sites as $site_id => $sharing_site) {
+        $form['settings']['sharing_sites_order'][$site_id] = array(
+          'content' => array(
+            '#markup' => $sharing_site,
+          ),
+          'weight' => array(
+            '#type' => 'weight',
+            '#title' => t('Weight for @title', array('@title' => $sharing_site)),
+            '#title_display' => 'invisible',
+            '#delta' => 50,
+            '#default_value' => isset($settings['sharing_sites_order'][$site_id]['weight']) ? $settings['sharing_sites_order'][$site_id]['weight'] : $sharing_sites_order_defaults[$site_id],
+            '#array_parents' => array('settings', 'sharing_sites_order', $site_id),
+          ),
+          '#weight' => isset($settings['sharing_sites_order'][$site_id]['weight']) ? $settings['sharing_sites_order'][$site_id]['weight'] : $sharing_sites_order_defaults[$site_id],
         );
-        // Add the element to the FAPI structure.
-        $form['settings']['plugins'][$plugin][$option] = $element;
       }
     }
+
+    $form['settings']['autostart'] = array(
+      '#title' => t('Autostart'),
+      '#type' => 'checkbox',
+      '#description' => t('Automatically start playing the video on page load. Can be true or false (default). Autostart does not work on mobile devices like iOS and Android.'),
+      '#default_value' => !empty($settings['autostart']) ? $settings['autostart'] : FALSE,
+      '#weight' => 4,
+    );
+
+    $form['settings']['mute'] = array(
+      '#type' => 'checkbox',
+      '#title' => t('Mute'),
+      '#description' => t('Mute the player by default during playback. This can be useful when combined with the autostart option. Cannot control settings of external sources such as YouTube.'),
+      '#default_value' => !empty($settings['mute']) ? $settings['mute'] : FALSE,
+      '#weight' => 4.5,
+    );
+  }
+  else {
+    $form['settings']['player_library_url'] = array(
+      '#type' => 'textfield',
+      '#title' => t('Player Library URL'),
+      '#description' => t('Enter the URL to the player created on JWPlayer.com that contains the settings for this preset.'),
+      '#required' => TRUE,
+    );
   }
 
-  $form['settings']['autostart'] = array(
-    '#title' => t('Autostart'),
-    '#type' => 'checkbox',
-    '#description' => t('Automatically start playing the video on page load. Can be true or false (default). Autostart does not work on mobile devices like iOS and Android.'),
-    '#default_value' => !empty($settings['autostart']) ? $settings['autostart']: FALSE,
-    '#weight' => 4,
+  $form['settings']['#attached']['js'] = array(
+    drupal_get_path('module', 'jw_player') . '/js/jw_player.admin.js',
   );
-
 }
 
 /**
  * Valdiator for jw_player_ctools_export_ui_form().
  */
 function jw_player_ctools_export_ui_form_validate($form, &$form_state) {
-  $vals = $form_state['values'];
-  if (!is_numeric($vals['settings']['width'])) {
-    form_set_error('width', 'Only numeric values allowed for width' );
+  $values = $form_state['values'];
+  // If sharing is empty, remove selected options from sharing sites
+  // field, and reset sharing heading text.
+  if (array_key_exists('sharing', $values['settings']) && $values['settings']['sharing'] == 0) {
+    $sharing = array();
+    foreach ($values['settings']['sharing_sites_show'] as $key => $value) {
+      $sharing[$key] = 0;
+    }
+    form_set_value($form['settings']['sharing_sites_show'], $sharing, $form_state);
+    form_set_value($form['settings']['sharing_heading'], t(''), $form_state);
   }
-  if (!is_numeric($vals['settings']['height'])) {
-    form_set_error('width', 'Only numeric values allowed for height' );
+
+  // Use preset form if legacy or a version that is configured with local presets.
+  if (jw_player_use_legacy() || jw_player_use_local_presets()) {
+    if ($values['settings']['responsive']) {
+      // Width Percent Validation.
+      if (empty($values['settings']['width']) || !is_numeric($values['settings']['width'])) {
+        form_set_error('settings][width', t('Width field is required and must be a numeric value.'));
+      }
+      else if ($values['settings']['width'] <= 0 || $values['settings']['width'] > 100) {
+        form_set_error('settings][width', t('Width field must be greater than 0% and no more than 100%.'));
+      }
+      // Aspect Ratio Validation.
+      if (empty($values['settings']['aspectratio'])) {
+        form_set_error('settings][aspectratio', t('Aspect Ratio field is required if Responsive Design is enabled.'));
+      }
+      else if (!preg_match('/^([0-9]*):([0-9]*)$/', $values['settings']['aspectratio'], $matches) ||
+        (!is_numeric($matches[1]) || !is_numeric($matches[2]))) {
+        form_set_error('settings][aspectratio', t('Aspect Ratio field must be of the format of two numbers separated by a colon. For example, <em>16:9</em>.'));
+      }
+    }
+    else {
+      // Width Validation.
+      if (empty($values['settings']['width']) || !is_numeric($values['settings']['width'])) {
+        form_set_error('settings][width', t('Width field is required and must be a numeric value.'));
+      }
+      else if ($values['settings']['width'] <= 0) {
+        form_set_error('settings][width', t('Width field must be greater than 0.'));
+      }
+      // Height Validation.
+      if (empty($values['settings']['height']) || !is_numeric($values['settings']['height'])) {
+        form_set_error('settings][height', t('Height field is required and must be a numeric value.'));
+      }
+      else if ($values['settings']['height'] <= 0) {
+        form_set_error('settings][height', t('Height field must be greater than 0.'));
+      }
+    }
+  }
+  else {
+    preg_match(jw_player_library_url_regex(), $values['settings']['library_player_url'], $matches);
+    if (!isset($matches[2])) {
+      form_set_error('settings][library_player_url', t('Player Library URL does not match format provided by JWPlayer.com.'));
+    }
   }
-}
\ No newline at end of file
+}
diff --git a/theme/jw_player.theme.inc b/theme/jw_player.theme.inc
new file mode 100644
index 0000000..07e32fd
--- /dev/null
+++ b/theme/jw_player.theme.inc
@@ -0,0 +1,45 @@
+<?php
+
+/**
+ * @file
+ * jw_player.theme.inc
+ *
+ * JW Player theme functions.
+ */
+
+/**
+ * Returns HTML for the sharing sites order form.
+ *
+ * Copied from the core theme_filter_admin_format_filter_order() function.
+ *
+ * @param $variables
+ *   An associative array containing:
+ *   - element: A render element representing the form.
+ *
+ * @ingroup themeable
+ */
+function theme_jw_player_sharing_sites_order($variables) {
+  $element = $variables['element'];
+
+  // JW Player sharing sites order (tabledrag).
+  $rows = array();
+  foreach (element_children($element, TRUE) as $name) {
+    $element[$name]['weight']['#attributes']['class'][] = 'jw-player-sharing-sites-order-weight';
+    $rows[] = array(
+      'data' => array(
+        drupal_render($element[$name]['content']),
+        drupal_render($element[$name]['weight']),
+      ),
+      'class' => array('draggable'),
+    );
+  }
+
+  $output = drupal_render_children($element);
+  $output .= theme('table', array(
+    'rows' => $rows,
+    'attributes' => array('id' => 'jw-player-sharing-sites-order')
+  ));
+  drupal_add_tabledrag('jw-player-sharing-sites-order', 'order', 'sibling', 'jw-player-sharing-sites-order-weight', NULL, NULL, TRUE);
+
+  return $output;
+}
-- 
2.8.1

