From 562e6f627929c39af5c86c674c9520c17cd90e5d Mon Sep 17 00:00:00 2001
From: Todd Bloom <todd.bloom@me.com>
Date: Thu, 7 Jan 2016 12:54:12 -0500
Subject: [PATCH] update code to work with latest drupal

---
 LICENSE.txt                                        | 339 ++++++++++
 config/install/salesforce.settings.yml             |  15 +
 config/salesforce.settings.yml                     |  14 -
 .../salesforce/Controller/SalesforceController.php |  36 --
 lib/Drupal/salesforce/Entity/SObject.php           |  75 ---
 .../salesforce/Form/SalesforceAuthorizeForm.php    | 130 ----
 .../Plugin/DataType/SalesforceReference.php        |  67 --
 lib/Drupal/salesforce/SalesforceClient.php         | 683 --------------------
 lib/Drupal/salesforce/SalesforceException.php      |  14 -
 modules/salesforce_pull/salesforce_pull.info       |   7 +
 modules/salesforce_soap/salesforce_soap.info       |   7 +
 salesforce.module                                  |  15 +-
 salesforce.permissions.yml                         |   4 +
 salesforce.services.yml                            |   2 +-
 src/Controller/SalesforceController.php            |  36 ++
 src/Entity/SObject.php.todo                        |  75 +++
 src/Form/SalesforceAuthorizeForm.php               | 145 +++++
 src/Plugin/DataType/SalesforceReference.php        |  67 ++
 src/SalesforceClient.php                           | 712 +++++++++++++++++++++
 src/SalesforceException.php                        |  14 +
 src/SalesforceSelectQuery.php                      |  95 +++
 21 files changed, 1518 insertions(+), 1034 deletions(-)
 create mode 100644 LICENSE.txt
 create mode 100644 config/install/salesforce.settings.yml
 delete mode 100644 config/salesforce.settings.yml
 delete mode 100644 lib/Drupal/salesforce/Controller/SalesforceController.php
 delete mode 100644 lib/Drupal/salesforce/Entity/SObject.php
 delete mode 100644 lib/Drupal/salesforce/Form/SalesforceAuthorizeForm.php
 delete mode 100644 lib/Drupal/salesforce/Plugin/DataType/SalesforceReference.php
 delete mode 100644 lib/Drupal/salesforce/SalesforceClient.php
 delete mode 100644 lib/Drupal/salesforce/SalesforceException.php
 create mode 100644 salesforce.permissions.yml
 create mode 100644 src/Controller/SalesforceController.php
 create mode 100644 src/Entity/SObject.php.todo
 create mode 100644 src/Form/SalesforceAuthorizeForm.php
 create mode 100644 src/Plugin/DataType/SalesforceReference.php
 create mode 100644 src/SalesforceClient.php
 create mode 100644 src/SalesforceException.php
 create mode 100644 src/SalesforceSelectQuery.php

diff --git a/LICENSE.txt b/LICENSE.txt
new file mode 100644
index 0000000..d159169
--- /dev/null
+++ b/LICENSE.txt
@@ -0,0 +1,339 @@
+                    GNU GENERAL PUBLIC LICENSE
+                       Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                            Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                    GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+                            NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+                     END OF TERMS AND CONDITIONS
+
+            How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License along
+    with this program; if not, write to the Free Software Foundation, Inc.,
+    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) year name of author
+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+  `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+  <signature of Ty Coon>, 1 April 1989
+  Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
diff --git a/config/install/salesforce.settings.yml b/config/install/salesforce.settings.yml
new file mode 100644
index 0000000..a51f12b
--- /dev/null
+++ b/config/install/salesforce.settings.yml
@@ -0,0 +1,15 @@
+consumer_key: ''
+consumer_secret: ''
+login_url: 'https://login.salesforce.com'
+refresh_token: ''
+access_token: ''
+instance_url: ''
+rest_api_version:
+  label: "Spring '13"
+  url: "/services/data/v27.0/"
+  version: "27.0"
+identity:
+  urls:
+    enterprise: ''
+    partner: ''
+    rest: ''
diff --git a/config/salesforce.settings.yml b/config/salesforce.settings.yml
deleted file mode 100644
index ad6daee..0000000
--- a/config/salesforce.settings.yml
+++ /dev/null
@@ -1,14 +0,0 @@
-consumer_key: ''
-consumer_secret: ''
-login_url: 'https://login.salesforce.com'
-refresh_token: ''
-instance_url: ''
-rest_api_version:
-  label: "Spring '13"
-  url: "/services/data/v27.0/"
-  version: "27.0"
-identity:
-  urls:
-    enterprise: ''
-    partner: ''
-    rest: ''
diff --git a/lib/Drupal/salesforce/Controller/SalesforceController.php b/lib/Drupal/salesforce/Controller/SalesforceController.php
deleted file mode 100644
index d58b40b..0000000
--- a/lib/Drupal/salesforce/Controller/SalesforceController.php
+++ /dev/null
@@ -1,36 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains \Drupal\salesforce\Controller\SalesforceController.
- */
-
-namespace Drupal\salesforce\Controller;
-
-use Drupal\Core\Controller\ControllerBase;
-use Symfony\Component\HttpFoundation\RedirectResponse;
-use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
-
-/**
- *
- */
-class SalesforceController extends ControllerBase {
-
-  /**
-   * Callback for the oauth redirect URI.
-   *
-   * Exchanges an authorization code for an access token.
-   */
-  public function oauthCallback() {
-    // If no code is provided, return access denied.
-    if (!isset($_GET['code'])) {
-      throw new AccessDeniedHttpException();
-    }
-    $salesforce = salesforce_get_api();
-    $salesforce->requestToken($_GET['code']);
-
-    salesforce_set_message('Salesforce OAUTH2 authorization successful.');
-
-    return new RedirectResponse(url('admin/config/salesforce/authorize', array('absolute' => TRUE)));
-  }
-}
diff --git a/lib/Drupal/salesforce/Entity/SObject.php b/lib/Drupal/salesforce/Entity/SObject.php
deleted file mode 100644
index 2505c76..0000000
--- a/lib/Drupal/salesforce/Entity/SObject.php
+++ /dev/null
@@ -1,75 +0,0 @@
-<?php
-
-/**
- * @file
- * Definition of \Drupal\salesforce\Entity\SObject.
- */
-
-namespace Drupal\salesforce\Entity;
-
-/**
- * An object representing a Salesforce Data Record
- * Adapted for Drupal from Salesforce PHP Toolkit
- * @see https://github.com/developerforce/Force.com-Toolkit-for-PHP
- * @todo figure out if this is the right approach
- * @todo implement a storage controller that makes sense
- * @EntityType(
- *   id = "sobject",
- *   label = @Translation("Salesforce Object"),
- *   controllers = {
- *     "storage" = "Drupal\file\NullStorage",
- *     "view_builder" = "Drupal\Core\Entity\EntityViewBuilder"
- *   },
- *   entity_keys = {
- *      "id" = "id",
- *      "bundle" = "type"
- *   }
- * )
- * @todo we'll probably need Dependency Injection of salesforce.client service
- */
-class SObject extends Entity {
-  /**
-   * The Salesforce ID of this SObject
-   */
-  public $id;
-
-  /**
-   * The fields for this SObject
-   */
-  public $fields;
-
-  /**
-   * The Salesforce Object Type
-   */
-  public $type;
-
-  /**
-   * SFID is universally unique across all SF instances.
-   */
-  public function uuid() {
-    return $this->id();
-  }
-
-  /**
-   * The URI for SObjects is external
-   */
-  public function uri($rel = 'cannonical') {
-    // @todo get the SF instance_url and build the correct URL here:
-    // @todo respet the $rel param
-    return 'http://www.salesforce.com/' . $this->id();
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  protected function uriPlaceholderReplacements() {
-    if (empty($this->uriPlaceholderReplacements)) {
-      $this->uriPlaceholderReplacements = array(
-        '{salesforce}' => $this->id(),
-        '{salesforce_type}' => $this->bundle(),
-      );
-    }
-    return $this->uriPlaceholderReplacements;
-  }
-}
-
diff --git a/lib/Drupal/salesforce/Form/SalesforceAuthorizeForm.php b/lib/Drupal/salesforce/Form/SalesforceAuthorizeForm.php
deleted file mode 100644
index 70bdae6..0000000
--- a/lib/Drupal/salesforce/Form/SalesforceAuthorizeForm.php
+++ /dev/null
@@ -1,130 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains \Drupal\salesforce\Form\SalesforceAuthorizeForm.
- */
-
-namespace Drupal\salesforce\Form;
-
-use Drupal\Core\Config\ConfigFactory;
-use Drupal\Core\Config\Context\ContextInterface;
-use Drupal\Core\Form\ConfigFormBase;
-use Drupal\salesforce\SalesforceClient;
-use Drupal\salesforce\SalesforceException;
-use Symfony\Component\DependencyInjection\ContainerInterface;
-
-/**
- * Creates authorization form for Salesforce.
- */
-class SalesforceAuthorizeForm extends ConfigFormBase {
-
-  protected $sf_client;
-
-  /**
-   * Constructs a \Drupal\system\ConfigFormBase object.
-   *
-   * @param \Drupal\Core\Config\ConfigFactory $config_factory
-   *   The factory for configuration objects.
-   * @param \Drupal\Core\Config\Context\ContextInterface $context
-   *   The configuration context to use.
-   * @param \Drupal\salesforce\SalesforceClient $sf_client
-   *   The factory for configuration objects.
-   */
-  public function __construct(ConfigFactory $config_factory, ContextInterface $context, SalesforceClient $salesforce_client) {
-    parent::__construct($config_factory, $context);
-    $this->sf_client = $salesforce_client;
-  }
-  
-  /**
-   * {@inheritdoc}
-   */
-  public static function create(ContainerInterface $container) {
-    return new static(
-      $container->get('config.factory'),
-      $container->get('config.context.free'),
-      $container->get('salesforce.client')
-    );
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getFormID() {
-    return 'salesforce_oauth';
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function buildForm(array $form, array &$form_state) {
-    $config = $this->config('salesforce.settings');
-
-    $form['message'] = array(
-      '#type' => 'item',
-      '#markup' => $this->t('Authorize this website to communicate with Salesforce by entering the consumer key and secret from a remote application. Clicking authorize will redirect you to Salesforce where you will be asked to grant access.'),
-    );
-
-    $form['consumer_key'] = array(
-      '#title' => $this->t('Salesforce consumer key'),
-      '#type' => 'textfield',
-      '#description' => $this->t('Consumer key of the Salesforce remote application you want to grant access to'),
-      '#required' => TRUE,
-      '#default_value' => $config->get('consumer_key'),
-    );
-    $form['consumer_secret'] = array(
-      '#title' => $this->t('Salesforce consumer secret'),
-      '#type' => 'textfield',
-      '#description' => $this->t('Consumer secret of the Salesforce remote application you want to grant access to'),
-      '#required' => TRUE,
-      '#default_value' => $config->get('consumer_secret'),
-    );
-
-    // If we're authenticated, show a list of available REST resources.
-    if ($config->get('consumer_key') && $config->get('consumer_secret')) {
-      // If fully configured, attempt to connect to Salesforce and return a list
-      // of resources.
-      if ($this->sf_client->isAuthorized()) {
-        try {
-          $resources = $this->sf_client->listResources();
-          foreach ($resources as $key => $path) {
-            $items[] = $key . ': ' . $path;
-          }
-          $form['resources'] = array(
-            '#title' => $this->t('Your Salesforce instance is authorized and has access to the following resources:'),
-            '#type' => 'item',
-            '#markup' => theme('item_list', array('items' => $items)),
-          );
-        }
-        catch(SalesforceException $e) {
-          salesforce_set_message($e->getMessage(), 'warning');
-        }
-      }
-      else {
-        salesforce_set_message(t('Salesforce needs to be authorized to connect to this website.'), 'error');
-      }
-    }
-
-    return parent::buildForm($form, $form_state);
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function submitForm(array &$form, array &$form_state) {
-    $config = $this->config('salesforce.settings')
-      ->set('consumer_key', $form_state['values']['consumer_key'])
-      ->set('consumer_secret', $form_state['values']['consumer_secret'])
-      ->save();
-
-    try {
-      return $this->sf_client->getAuthorizationCode();
-    }
-    catch (SalesforceException $e) {
-      // Set form error
-    }
-
-    parent::submitForm($form, $form_state);
-  }
-
-}
diff --git a/lib/Drupal/salesforce/Plugin/DataType/SalesforceReference.php b/lib/Drupal/salesforce/Plugin/DataType/SalesforceReference.php
deleted file mode 100644
index c6845d8..0000000
--- a/lib/Drupal/salesforce/Plugin/DataType/SalesforceReference.php
+++ /dev/null
@@ -1,67 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains \Drupal\salesforce\Plugin\DataType\SalesforceReference
- */
-
-namespace Drupal\salesforce\Plugin\DataType\;
-
-use Drupal\Core\TypedData\Annotation\DataType;
-use Drupal\Core\Annotation\Translation;
-use Drupal\Core\TypedData\DataReferenceBase;
-
-/**
- * Defines a 'salesforce_reference' data type.
- *
- * This serves as 'salesforce' property of salesforce reference field items.
- *
- * The plain value of this reference is a sobject record.
- * For setting the value, the salesforce object record or the salesforce ID may
- * be passed.
- *
- * Some supported constraints (below the definition's 'constraints' key) are:
- *  - Bundle: (optional) The bundle (salesforce object type) or an array of
- *              possible bundles.
- *
- * @DataType(
- *   id = "salesforce_reference",
- *   label = @Translation("Salesforce reference")
- * )
- */
-class SalesforceReference extends DataReferenceBase {
-
-  /**
-   * The Salesforce ID
-   *
-   * @var string
-   */
-  protected $sfid;
-
-  // @todo add useful things like object type and/or record type, and an actual reference to the data instead of just the ID
-  
-  /**
-   * {@inheritdoc}
-   */
-  public function getTargetDefinition() {
-    return array(
-      'type' => 'sobject',
-    );
-  }
-
-  /**
-   * {@inheritdoc}
-   * @todo retrieve the Salesforce Object record from cache instead of just the id.
-   */
-  public function getTarget() {
-    return $this->sfid;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getTargetIdentifier() {
-    return $this->sfid;
-  }
-
-}
\ No newline at end of file
diff --git a/lib/Drupal/salesforce/SalesforceClient.php b/lib/Drupal/salesforce/SalesforceClient.php
deleted file mode 100644
index 8635f74..0000000
--- a/lib/Drupal/salesforce/SalesforceClient.php
+++ /dev/null
@@ -1,683 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains \Drupal\salesforce\SalesforceClient.
- */
-
-namespace Drupal\salesforce;
-
-use Drupal\Component\Utility\Json;
-use Drupal\Core\Cache\CacheBackendInterface;
-use Drupal\Core\Config\ConfigFactory;
-use Guzzle\Http\Exception\RequestException;
-use Guzzle\Http\ClientInterface;
-use Symfony\Component\HttpFoundation\RedirectResponse;
-
-/**
- * Objects, properties, and methods to communicate with the Salesforce REST API.
- */
-class SalesforceClient {
-
-  public $response;
-  protected $httpClient;
-  protected $configFactory;
-  private $config;
-
-  /**
-   * Constructor which initializes the consumer.
-   * @param \Drupal\Core\Config\ConfigFactory $config_factory
-   *   The config factory
-   * @param \Guzzle\Http\ClientInterface $http_client
-   *   The config factory
-   */
-  public function __construct(ClientInterface $http_client, ConfigFactory $config_factory) {
-    $this->configFactory = $config_factory;
-    $this->httpClient = $http_client;
-    $this->config = $this->configFactory->get('salesforce.settings');
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public static function create(ContainerInterface $container) {
-    return new static(
-      $container->get('http_default_client'),
-      $container->get('config.factory')
-    );
-  }
-
-  /**
-   * Determine if this SF instance is fully configured.
-   *
-   * @TODO: Consider making a test API call.
-   */
-  public function isAuthorized() {
-    return $this->getConsumerKey() && $this->getConsumerSecret() && $this->getRefreshToken();
-  }
-
-  /**
-   * Make a call to the Salesforce REST API.
-   *
-   * @param string $path
-   *   Path to resource.
-   * @param array $params
-   *   Parameters to provide.
-   * @param string $method
-   *   Method to initiate the call, such as GET or POST.  Defaults to GET.
-   *
-   * @return mixed
-   *   The requested response.
-   *
-   * @throws SalesforceException
-   */
-  public function apiCall($path, $params = array(), $method = 'GET') {
-
-    if (!$this->getAccessToken()) {
-      $this->refreshToken();
-    }
-
-    try {
-      $this->response = $this->apiHttpRequest($path, $params, $method);
-    }
-    catch (RequestException $e) {
-      // A RequestException gets thrown if the response has any error status.
-      $this->response = $e->getRequest()->getResponse();
-    }
-
-    if (!is_object($this->response)) {
-      throw new SalesforceException('Unknown error occurred during API call');
-    }
-
-    switch ($this->response->getStatusCode()) {
-      case 401:
-        // The session ID or OAuth token used has expired or is invalid: refresh
-        // token. If refreshToken() throws an exception, or if apiHttpRequest()
-        // throws anything but a RequestException, let it bubble up.
-        $this->refreshToken();
-        try {
-          $this->response = $this->apiHttpRequest($path, $params, $method);
-        }
-        catch (RequestException $e) {
-          $this->response = $e->getRequest()->getResponse();
-          throw new SalesforceException($this->response->getReasonPhrase(), $this->response->getStatusCode());
-        }
-        break;
-      case 200:
-      case 201:
-      case 204:
-        // All clear.
-        break;
-
-      default:
-        // We have problem and no specific Salesforce error provided.
-        if (empty($this->response)) {
-          throw new SalesforceException('Unknown error occurred during API call');
-        }
-    }
-
-    try {
-      $data = $this->response->json();
-    }
-    catch (\RuntimeException $e) {
-      throw new SalesforceException('Unable to parse API response.');
-    }
-
-    if (!empty($data[0]) && count($data) == 1) {
-      $data = $data[0];
-    }
-
-    if (isset($data['error'])) {
-      throw new SalesforceException($data['error_description'], $data['error']);
-    }
-
-    if (!empty($data['errorCode'])) {
-      throw new SalesforceException($data['message'], $this->response->getStatusCode());
-    }
-
-    return $data;
-  }
-
-  /**
-   * Private helper to issue an SF API request.
-   *
-   * @param string $path
-   *   Path to resource.
-   * @param array $params
-   *   Parameters to provide.
-   * @param string $method
-   *   Method to initiate the call, such as GET or POST.  Defaults to GET.
-   *
-   * @return object
-   *   The requested data.
-   */
-  protected function apiHttpRequest($path, $params, $method) {
-    if (!$this->getAccessToken()) {
-      throw new SalesforceException('Missing OAuth Token');
-    }
-    $url = $this->getApiEndPoint() . $path;
-    $headers = array(
-      'Authorization' => 'OAuth ' . $this->getAccessToken(),
-      'Content-type' => 'application/json',
-    );
-    $data = NULL;
-    if (!empty($params)) {
-      // @todo: convert this into Dependency Injection
-      $data =  Json::encode($params);
-    }
-    return $this->httpRequest($url, $data, $headers, $method);
-  }
-
-  /**
-   * Make the HTTP request. Wrapper around drupal_http_request().
-   *
-   * @param string $url
-   *   Path to make request from.
-   * @param array $data
-   *   The request body.
-   * @param array $headers
-   *   Request headers to send as name => value.
-   * @param string $method
-   *   Method to initiate the call, such as GET or POST.  Defaults to GET.
-   *
-   * @throws RequestException
-   *
-   * @return object
-   *   Salesforce response object.
-   */
-  protected function httpRequest($url, $data = NULL, $headers = array(), $method = 'GET') {
-    // Build the request, including path and headers. Internal use.
-    $request = $this->httpClient->$method($url, $headers, $data);
-    $request->send();
-    return $request->getResponse();
-  }
-
-  /**
-   * Get the API end point for a given type of the API.
-   *
-   * @param string $api_type
-   *   E.g., rest, partner, enterprise.
-   *
-   * @return string
-   *   Complete URL endpoint for API access.
-   */
-  public function getApiEndPoint($api_type = 'rest') {
-    $url = &drupal_static(__FUNCTION__ . $api_type);
-    if (!isset($url)) {
-      $identity = $this->getIdentity();
-      if (is_string($identity)) {
-        $url = $identity;
-      }
-      elseif (isset($identity['urls'][$api_type])) {
-        $url = $identity['urls'][$api_type];
-      }
-      $url = str_replace('{version}', $this->config->get('rest_api_version.version'), $url);
-    }
-    return $url;
-  }
-
-  public function getConsumerKey() {
-    return $this->config->get('consumer_key');
-  }
-
-  public function setConsumerKey($value) {
-    return $this->config->set('consumer_key', $value)->save();
-  }
-
-  public function getConsumerSecret() {
-    return $this->config->get('consumer_secret');
-  }
-
-  public function setConsumerSecret($value) {
-    return $this->config->set('consumer_secret', $value)->save();
-  }
-  /**
-   * Get the SF instance URL. Useful for linking to objects.
-   */
-  public function getInstanceUrl() {
-    return $this->config->get('instance_url');
-  }
-
-  /**
-   * Set the SF instanc URL.
-   *
-   * @param string $url
-   *   URL to set.
-   */
-  protected function setInstanceUrl($url) {
-    $this->config->set('instance_url', $url)->save();
-  }
-
-  /**
-   * Get the access token.
-   */
-  public function getAccessToken() {
-    // @todo There is probably a better way to do this in D8.
-    // Why not put it in settings?
-    return isset($_SESSION['salesforce_access_token']) ? $_SESSION['salesforce_access_token'] : FALSE;
-  }
-
-  /**
-   * Set the access token.
-   *
-   * It is stored in session.
-   *
-   * @param string $token
-   *   Access token from Salesforce.
-   */
-  protected function setAccessToken($token) {
-    // @todo There is probably a better way to do this in D8.
-    // Why not put it in settings?
-    $_SESSION['salesforce_access_token'] = $token;
-  }
-
-  /**
-   * Get refresh token.
-   */
-  protected function getRefreshToken() {
-    return $this->config->get('refresh_token');
-  }
-
-  /**
-   * Set refresh token.
-   *
-   * @param string $token
-   *   Refresh token from Salesforce.
-   */
-  protected function setRefreshToken($token) {
-    $this->config->set('refresh_token', $token)->save();
-  }
-
-  /**
-   * Refresh access token based on the refresh token. Updates session variable.
-   *
-   * @throws SalesforceException
-   */
-  protected function refreshToken() {
-    $refresh_token = $this->getRefreshToken();
-    if (empty($refresh_token)) {
-      throw new SalesforceException(t('There is no refresh token.'));
-    }
-
-    $data = drupal_http_build_query(array(
-      'grant_type' => 'refresh_token',
-      'refresh_token' => $refresh_token,
-      'client_id' => $this->getConsumerKey(),
-      'client_secret' => $this->getConsumerSecret(),
-    ));
-
-    $url = $this->config->get('login_url') . '/services/oauth2/token';
-    $headers = array(
-      // This is an undocumented requirement on Salesforce's end.
-      'Content-Type' => 'application/x-www-form-urlencoded',
-    );
-    $response = $this->httpRequest($url, $data, $headers, 'POST');
-
-    if ($response->isError()) {
-      // @TODO: Deal with error better.
-      throw new SalesforceException(t('Unable to get a Salesforce access token.'), $response->getStatusCode());
-    }
-
-    try {
-      $data = $response->json();
-    }
-    catch (\RuntimeException $e) {
-      throw new SalesforceException($e->getMessage(), $e->getCode());
-    }
-
-    if (isset($data['error'])) {
-      throw new SalesforceException($data['error_description'], $data['error']);
-    }
-
-    $this->setAccessToken($data['access_token']);
-    $this->setIdentity($data['id']);
-    $this->config
-      ->set('instance_url', $data['instance_url'])
-      ->save();
-  }
-
-  /**
-   * Retrieve and store the Salesforce identity given an ID url.
-   *
-   * @param string $id
-   *   Identity URL.
-   *
-   * @throws SalesforceException
-   */
-  protected function setIdentity($id) {
-    $headers = array(
-      'Authorization' => 'OAuth ' . $this->getAccessToken(),
-      'Content-type' => 'application/json',
-    );
-    $response = $this->httpRequest($id, NULL, $headers);
-    if ($response->isError()) {
-      throw new SalesforceException(t('Unable to access identity service.'), $response->getStatusCode());
-    }
-
-    try {
-      $data = $response->json();
-    }
-    catch (\RuntimeException $e) {
-      throw new SalesforceException($e->getMessage(), $e->getCode());
-    }
-
-    $this->config->set('identity', $data)->save();
-  }
-
-  /**
-   * Return the Salesforce identity, which is stored in a variable.
-   *
-   * @return array
-   *   Returns FALSE is no identity has been stored.
-   */
-  public function getIdentity() {
-    return $this->config->get('identity');
-  }
-
-  /**
-   * OAuth step 1: Redirect to Salesforce and request and authorization code.
-   */
-  public function getAuthorizationCode() {
-    $path = $this->config->get('login_url') . '/services/oauth2/authorize';
-    $query = array(
-      'redirect_uri' => $this->redirectUrl(),
-      'response_type' => 'code',
-      'client_id' => $this->getConsumerKey(),
-    );
-
-    $response = new RedirectResponse(url($path, array('query' => $query, 'absolute' => TRUE)));
-    $response->send();
-  }
-
-  /**
-   * OAuth step 2: Exchange an authorization code for an access token.
-   *
-   * @param string $code
-   *   Code from Salesforce.
-   */
-  public function requestToken($code) {
-    $data = drupal_http_build_query(array(
-      'code' => $code,
-      'grant_type' => 'authorization_code',
-      'client_id' => $this->getConsumerKey(),
-      'client_secret' => $this->getConsumerSecret(),
-      'redirect_uri' => $this->redirectUrl(),
-    ));
-    $url = $this->config->get('login_url') . '/services/oauth2/token';
-    $headers = array(
-      // This is an undocumented requirement on SF's end.
-      'Content-Type' => 'application/x-www-form-urlencoded',
-    );
-    $response = $this->httpRequest($url, $data, $headers, 'POST');
-
-    if ($response->isError()) {
-      throw new SalesforceException($response->getReasonPhrase(), $response->getStatusCode());
-    }
-
-    try {
-      $data = $response->json();
-    }
-    catch (\RuntimeException $e) {
-      throw new SalesforceException($e->getMessage(), $e->getCode());
-    }
-
-    $this->setAccessToken($data['access_token']);
-    $this->setIdentity($data['id']);
-    $this->config
-      ->set('refresh_token', $data['refresh_token'])
-      ->set('instance_url', $data['instance_url'])
-      ->save();
-  }
-
-  /**
-   * Helper to build the redirect URL for OAUTH workflow.
-   *
-   * @return string
-   *   Redirect URL.
-   */
-  protected function redirectUrl() {
-    return url('salesforce/oauth_callback', array(
-      'absolute' => TRUE,
-      'https' => TRUE,
-    ));
-  }
-
-  /**
-   * @defgroup salesforce_apicalls Wrapper calls around core apiCall()
-   */
-
-  /**
-   * Available objects and their metadata for your organization's data.
-   *
-   * @param array $conditions
-   *   Associative array of filters to apply to the returned objects. Filters
-   *   are applied after the list is returned from Salesforce.
-   * @param bool $reset
-   *   Whether to reset the cache and retrieve a fresh version from Salesforce.
-   *
-   * @return array
-   *   Available objects and metadata.
-   *
-   * @addtogroup salesforce_apicalls
-   */
-  public function objects($conditions = array('updateable' => TRUE), $reset = FALSE) {
-    $cache = cache()->get('salesforce:objects');
-    // Force the recreation of the cache when it is older than 5 minutes.
-    if ($cache && REQUEST_TIME < ($cache->created + 300) && !$reset) {
-      $result = $cache->data;
-    }
-    else {
-      $result = $this->apiCall('sobjects');
-
-      // Allow the cache to clear at any time by not setting an expire time.
-      // CACHE_TEMPORARY has been removed. Using 'content' tag to replicate
-      // old functionality.
-      // @see https://drupal.org/node/1534648
-      cache()->set('salesforce:objects', $result, CacheBackendInterface::CACHE_PERMANENT, array('salesforce' => TRUE, 'content' => TRUE));
-    }
-
-    if (!empty($conditions)) {
-      foreach ($result['sobjects'] as $key => $object) {
-        foreach ($conditions as $condition => $value) {
-          if (!$object[$condition] == $value) {
-            unset($result['sobjects'][$key]);
-          }
-        }
-      }
-    }
-
-    return $result['sobjects'];
-  }
-
-  /**
-   * Use SOQL to get objects based on query string.
-   *
-   * @param SalesforceSelectQuery $query
-   *   The constructed SOQL query.
-   *
-   * @return array
-   *   Array of Salesforce objects that match the query.
-   *
-   * @addtogroup salesforce_apicalls
-   */
-  public function query(SalesforceSelectQuery $query) {
-    drupal_alter('salesforce_query', $query);
-    // Casting $query as a string calls SalesforceSelectQuery::__toString().
-    $result = $this->apiCall('query?q=' . (string) $query);
-    return $result;
-  }
-
-  /**
-   * Retreieve all the metadata for an object.
-   *
-   * @param string $name
-   *   Object type name, E.g., Contact, Account, etc.
-   * @param bool $reset
-   *   Whether to reset the cache and retrieve a fresh version from Salesforce.
-   *
-   * @return array
-   *   All the metadata for an object, including information about each field,
-   *   URLs, and child relationships.
-   *
-   * @addtogroup salesforce_apicalls
-   */
-  public function objectDescribe($name, $reset = FALSE) {
-    if (empty($name)) {
-      return array();
-    }
-    $cache = cache()->get('salesforce:object:' . $name);
-    // Force the recreation of the cache when it is older than 5 minutes.
-    if ($cache && REQUEST_TIME < ($cache->created + 300) && !$reset) {
-      return $cache->data;
-    }
-    else {
-      $object = $this->apiCall("sobjects/{$name}/describe");
-      // Allow the cache to clear at any time by not setting an expire time.
-      // CACHE_TEMPORARY has been removed. Using 'content' tag to replicate
-      // old functionality. @see https://drupal.org/node/1534648
-      cache()->set('salesforce:object:' . $name, $object, CacheBackendInterface::CACHE_PERMANENT, array('salesforce' => TRUE, 'content' => TRUE));
-      return $object;
-    }
-  }
-
-  /**
-   * Create a new object of the given type.
-   *
-   * @param string $name
-   *   Object type name, E.g., Contact, Account, etc.
-   * @param array $params
-   *   Values of the fields to set for the object.
-   *
-   * @return array
-   *   "id" : "001D000000IqhSLIAZ",
-   *   "errors" : [ ],
-   *   "success" : true
-   *
-   * @addtogroup salesforce_apicalls
-   */
-  public function objectCreate($name, $params) {
-    return $this->apiCall("sobjects/{$name}", $params, 'POST');
-  }
-
-  /**
-   * Create new records or update existing records.
-   *
-   * The new records or updated records are based on the value of the specified
-   * field.  If the value is not unique, REST API returns a 300 response with
-   * the list of matching records.
-   *
-   * @param string $name
-   *   Object type name, E.g., Contact, Account.
-   * @param string $key
-   *   The field to check if this record should be created or updated.
-   * @param string $value
-   *   The value for this record of the field specified for $key.
-   * @param array $params
-   *   Values of the fields to set for the object.
-   *
-   * @return array
-   *   successful create:
-   *     "id" : "00190000001pPvHAAU",
-   *     "errors" : [ ],
-   *     "success" : true
-   *   error:
-   *     "message" : "The requested resource does not exist"
-   *     "errorCode" : "NOT_FOUND"
-   *
-   * @addtogroup salesforce_apicalls
-   */
-  public function objectUpsert($name, $key, $value, $params) {
-    // If key is set, remove from $params to avoid UPSERT errors.
-    if (isset($params[$key])) {
-      unset($params[$key]);
-    }
-    // @todo handle "duplicate external id" response
-    // @see https://drupal.org/node/2140417
-    $data = $this->apiCall("sobjects/{$name}/{$key}/{$value}", $params, 'PATCH');
-    return $data;
-  }
-
-  /**
-   * Update an existing object.
-   *
-   * @param string $name
-   *   Object type name, E.g., Contact, Account.
-   * @param string $id
-   *   Salesforce id of the object.
-   * @param array $params
-   *   Values of the fields to set for the object.
-   *
-   * @return null
-   *
-   * @addtogroup salesforce_apicalls
-   */
-  public function objectUpdate($name, $id, $params) {
-    $this->apiCall("sobjects/{$name}/{$id}", $params, 'PATCH');
-  }
-
-  /**
-   * Return a full loaded Salesforce object.
-   *
-   * @param string $name
-   *   Object type name, E.g., Contact, Account.
-   * @param string $id
-   *   Salesforce id of the object.
-   *
-   * @return array
-   *   Object of the requested Salesforce object.
-   *
-   * @addtogroup salesforce_apicalls
-   */
-  public function objectRead($name, $id) {
-    return $this->apiCall("sobjects/{$name}/{$id}", array(), 'GET');
-  }
-
-  /**
-   * Return a full loaded Salesforce object given an external key
-   *
-   * @param string $name
-   *   Object type name, e.g. Contact, Account
-   * @param string $key_field
-   *   External key field name, e.g. External_ID__c
-   * @param string $key_value
-   *   External key value, e.g. $node->nid, $user->uid
-   *
-   * @return array
-   *   Object of the requested Salesforce object
-   *
-   * @addtogroup salesforce_apicalls
-   */
-  public function objectReadKey($name, $key_field, $key_value) {
-    return $this->apiCall("sobjects/{$naem}/{$key_field}/{$key_value}", 'GET');
-  }
-
-  /**
-   * Delete a Salesforce object.
-   *
-   * @param string $name
-   *   Object type name, E.g., Contact, Account.
-   * @param string $id
-   *   Salesforce id of the object.
-   *
-   * @addtogroup salesforce_apicalls
-   */
-  public function objectDelete($name, $id) {
-    $this->apiCall("sobjects/{$name}/{$id}", array(), 'DELETE');
-  }
-
-  /**
-   * Return a list of available resources for the configured API version.
-   *
-   * @return array
-   *   Associative array keyed by name with a URI value.
-   *
-   * @addtogroup salesforce_apicalls
-   */
-  public function listResources() {
-    $resources = $this->apiCall('');
-    foreach ($resources as $key => $path) {
-      $items[$key] = $path;
-    }
-    return $items;
-  }
-}
diff --git a/lib/Drupal/salesforce/SalesforceException.php b/lib/Drupal/salesforce/SalesforceException.php
deleted file mode 100644
index 564572a..0000000
--- a/lib/Drupal/salesforce/SalesforceException.php
+++ /dev/null
@@ -1,14 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains \Drupal\salesforce\SalesforceException.
- */
-
-namespace Drupal\salesforce;
-
-use Symfony\Component\Serializer\Exception\Exception;
-
-class SalesforceException extends \RuntimeException implements Exception {
-
-}
diff --git a/modules/salesforce_pull/salesforce_pull.info b/modules/salesforce_pull/salesforce_pull.info
index c58b335..f908042 100644
--- a/modules/salesforce_pull/salesforce_pull.info
+++ b/modules/salesforce_pull/salesforce_pull.info
@@ -5,3 +5,10 @@ core = 7.x
 
 dependencies[] = salesforce_mapping
 
+
+; Information added by Drupal.org packaging script on 2013-12-05
+version = "8.x-3.x-dev"
+core = "8.x"
+project = "salesforce"
+datestamp = "1386269315"
+
diff --git a/modules/salesforce_soap/salesforce_soap.info b/modules/salesforce_soap/salesforce_soap.info
index a915553..ce586cd 100644
--- a/modules/salesforce_soap/salesforce_soap.info
+++ b/modules/salesforce_soap/salesforce_soap.info
@@ -7,3 +7,10 @@ files[] = salesforce_soap.inc
 
 dependencies[] = salesforce
 dependencies[] = libraries
+
+; Information added by Drupal.org packaging script on 2013-12-05
+version = "8.x-3.x-dev"
+core = "8.x"
+project = "salesforce"
+datestamp = "1386269315"
+
diff --git a/salesforce.module b/salesforce.module
index 2ad9393..6af57eb 100644
--- a/salesforce.module
+++ b/salesforce.module
@@ -82,7 +82,7 @@ function salesforce_help($path, $arg) {
 
 /**
  * Implements hook_menu().
- * @todo hook_menu is going away. this will likely be replaced by something like 
+ * @todo hook_menu is going away. this will likely be replaced by something like
  *   hook_default_menu_links()
  * @see http://drupal.org/node/1800686
  * @see http://drupal.org/node/2047633
@@ -115,19 +115,6 @@ function salesforce_menu() {
 }
 
 /**
- * Implements hook_permission().
- */
-function salesforce_permission() {
-  return array(
-    'administer salesforce' => array(
-      'title' => t('administer salesforce'),
-      'description' => t('Administer Salesforce integration.'),
-      'restrict access' => TRUE,
-    ),
-  );
-}
-
-/**
  * Implements hook_flush_caches().
  */
 function salesforce_cache_flush() {
diff --git a/salesforce.permissions.yml b/salesforce.permissions.yml
new file mode 100644
index 0000000..150adc6
--- /dev/null
+++ b/salesforce.permissions.yml
@@ -0,0 +1,4 @@
+administer salesforce:
+  title: 'administer salesforce'
+  description: 'Administer Salesforce integration'
+  restrict access: TRUE
diff --git a/salesforce.services.yml b/salesforce.services.yml
index 945e62c..bee8583 100644
--- a/salesforce.services.yml
+++ b/salesforce.services.yml
@@ -1,4 +1,4 @@
 services:
   salesforce.client:
     class: Drupal\salesforce\SalesforceClient
-    arguments: ['@http_default_client', '@config.factory']
\ No newline at end of file
+    arguments: ['@http_client', '@config.factory', '@url_generator']
diff --git a/src/Controller/SalesforceController.php b/src/Controller/SalesforceController.php
new file mode 100644
index 0000000..94e1ceb
--- /dev/null
+++ b/src/Controller/SalesforceController.php
@@ -0,0 +1,36 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\salesforce\Controller\SalesforceController.
+ */
+
+namespace Drupal\salesforce\Controller;
+
+use Drupal\Core\Controller\ControllerBase;
+use Symfony\Component\HttpFoundation\RedirectResponse;
+use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
+
+/**
+ *
+ */
+class SalesforceController extends ControllerBase {
+
+  /**
+   * Callback for the oauth redirect URI.
+   *
+   * Exchanges an authorization code for an access token.
+   */
+  public function oauthCallback() {
+    // If no code is provided, return access denied.
+    if (!isset($_GET['code'])) {
+      throw new AccessDeniedHttpException();
+    }
+    $salesforce = salesforce_get_api();
+    $salesforce->requestToken($_GET['code']);
+
+    salesforce_set_message('Salesforce OAUTH2 authorization successful.');
+
+    return new RedirectResponse(\Drupal::url('salesforce.authorize', array(), array('absolute' => TRUE)));
+  }
+}
diff --git a/src/Entity/SObject.php.todo b/src/Entity/SObject.php.todo
new file mode 100644
index 0000000..2505c76
--- /dev/null
+++ b/src/Entity/SObject.php.todo
@@ -0,0 +1,75 @@
+<?php
+
+/**
+ * @file
+ * Definition of \Drupal\salesforce\Entity\SObject.
+ */
+
+namespace Drupal\salesforce\Entity;
+
+/**
+ * An object representing a Salesforce Data Record
+ * Adapted for Drupal from Salesforce PHP Toolkit
+ * @see https://github.com/developerforce/Force.com-Toolkit-for-PHP
+ * @todo figure out if this is the right approach
+ * @todo implement a storage controller that makes sense
+ * @EntityType(
+ *   id = "sobject",
+ *   label = @Translation("Salesforce Object"),
+ *   controllers = {
+ *     "storage" = "Drupal\file\NullStorage",
+ *     "view_builder" = "Drupal\Core\Entity\EntityViewBuilder"
+ *   },
+ *   entity_keys = {
+ *      "id" = "id",
+ *      "bundle" = "type"
+ *   }
+ * )
+ * @todo we'll probably need Dependency Injection of salesforce.client service
+ */
+class SObject extends Entity {
+  /**
+   * The Salesforce ID of this SObject
+   */
+  public $id;
+
+  /**
+   * The fields for this SObject
+   */
+  public $fields;
+
+  /**
+   * The Salesforce Object Type
+   */
+  public $type;
+
+  /**
+   * SFID is universally unique across all SF instances.
+   */
+  public function uuid() {
+    return $this->id();
+  }
+
+  /**
+   * The URI for SObjects is external
+   */
+  public function uri($rel = 'cannonical') {
+    // @todo get the SF instance_url and build the correct URL here:
+    // @todo respet the $rel param
+    return 'http://www.salesforce.com/' . $this->id();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function uriPlaceholderReplacements() {
+    if (empty($this->uriPlaceholderReplacements)) {
+      $this->uriPlaceholderReplacements = array(
+        '{salesforce}' => $this->id(),
+        '{salesforce_type}' => $this->bundle(),
+      );
+    }
+    return $this->uriPlaceholderReplacements;
+  }
+}
+
diff --git a/src/Form/SalesforceAuthorizeForm.php b/src/Form/SalesforceAuthorizeForm.php
new file mode 100644
index 0000000..572eee5
--- /dev/null
+++ b/src/Form/SalesforceAuthorizeForm.php
@@ -0,0 +1,145 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\salesforce\Form\SalesforceAuthorizeForm.
+ */
+
+namespace Drupal\salesforce\Form;
+
+use Drupal\Core\Config\ConfigFactory;
+use Drupal\Core\Form\ConfigFormBase;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\salesforce\SalesforceClient;
+use Drupal\salesforce\SalesforceException;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Creates authorization form for Salesforce.
+ */
+class SalesforceAuthorizeForm extends ConfigFormBase {
+
+  protected $sf_client;
+
+  /**
+   * Constructs a \Drupal\system\ConfigFormBase object.
+   *
+   * @param \Drupal\Core\Config\ConfigFactory $config_factory
+   *   The factory for configuration objects.
+   * @param \Drupal\Core\Config\Context\ContextInterface $context
+   *   The configuration context to use.
+   * @param \Drupal\salesforce\SalesforceClient $sf_client
+   *   The factory for configuration objects.
+   */
+  public function __construct(ConfigFactory $config_factory, SalesforceClient $salesforce_client) {
+    parent::__construct($config_factory);
+    $this->sf_client = $salesforce_client;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static(
+      $container->get('config.factory'),
+      $container->get('salesforce.client')
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFormID() {
+    return 'salesforce_oauth';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getEditableConfigNames() {
+    return [
+      'salesforce.settings',
+    ];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildForm(array $form, FormStateInterface $form_state) {
+    $config = $this->config('salesforce.settings');
+
+    $form['message'] = array(
+      '#type' => 'item',
+      '#markup' => $this->t('Authorize this website to communicate with Salesforce by entering the consumer key and secret from a remote application. Clicking authorize will redirect you to Salesforce where you will be asked to grant access.'),
+    );
+
+    $form['consumer_key'] = array(
+      '#title' => $this->t('Salesforce consumer key'),
+      '#type' => 'textfield',
+      '#description' => $this->t('Consumer key of the Salesforce remote application you want to grant access to'),
+      '#required' => TRUE,
+      '#default_value' => $config->get('consumer_key'),
+    );
+    $form['consumer_secret'] = array(
+      '#title' => $this->t('Salesforce consumer secret'),
+      '#type' => 'textfield',
+      '#description' => $this->t('Consumer secret of the Salesforce remote application you want to grant access to'),
+      '#required' => TRUE,
+      '#default_value' => $config->get('consumer_secret'),
+    );
+    $form['login_url'] = array(
+      '#title' => $this->t('Login URL'),
+      '#type' => 'textfield',
+      '#default_value' => ((is_null($config->get('login_url'))) ? 'https://login.salesforce.com' : $config->get('login_url')),
+    );
+
+    // If we're authenticated, show a list of available REST resources.
+    if ($config->get('consumer_key') && $config->get('consumer_secret')) {
+      // If fully configured, attempt to connect to Salesforce and return a list
+      // of resources.
+      if ($this->sf_client->isAuthorized()) {
+        try {
+          $resources = $this->sf_client->listResources();
+          foreach ($resources as $key => $path) {
+            $items[] = $key . ': ' . $path;
+          }
+          $form['resources'] = array(
+            '#title' => $this->t('Your Salesforce instance is authorized and has access to the following resources:'),
+            '#items' => $items,
+            '#theme' => 'item_list'
+          );
+        }
+        catch(\GuzzleHttp\Exception\ClientException $e) {
+          salesforce_set_message($e->getMessage(), 'warning');
+        }
+      }
+      else {
+        salesforce_set_message(t('Salesforce needs to be authorized to connect to this website.'), 'error');
+      }
+    }
+
+    return parent::buildForm($form, $form_state);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function submitForm(array &$form, FormStateInterface $form_state) {
+    $values = $form_state->getValues();
+    $this->config('salesforce.settings')
+      ->set('consumer_key', $values['consumer_key'])
+      ->set('consumer_secret', $values['consumer_secret'])
+      ->set('login_url', $values['login_url'])
+      ->save();
+
+    try {
+      return $this->sf_client->getAuthorizationCode();
+    }
+    catch (\GuzzleHttp\Exception\ClientException $e) {
+      // Set form error
+    }
+
+    parent::submitForm($form, $form_state);
+  }
+
+}
diff --git a/src/Plugin/DataType/SalesforceReference.php b/src/Plugin/DataType/SalesforceReference.php
new file mode 100644
index 0000000..c6845d8
--- /dev/null
+++ b/src/Plugin/DataType/SalesforceReference.php
@@ -0,0 +1,67 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\salesforce\Plugin\DataType\SalesforceReference
+ */
+
+namespace Drupal\salesforce\Plugin\DataType\;
+
+use Drupal\Core\TypedData\Annotation\DataType;
+use Drupal\Core\Annotation\Translation;
+use Drupal\Core\TypedData\DataReferenceBase;
+
+/**
+ * Defines a 'salesforce_reference' data type.
+ *
+ * This serves as 'salesforce' property of salesforce reference field items.
+ *
+ * The plain value of this reference is a sobject record.
+ * For setting the value, the salesforce object record or the salesforce ID may
+ * be passed.
+ *
+ * Some supported constraints (below the definition's 'constraints' key) are:
+ *  - Bundle: (optional) The bundle (salesforce object type) or an array of
+ *              possible bundles.
+ *
+ * @DataType(
+ *   id = "salesforce_reference",
+ *   label = @Translation("Salesforce reference")
+ * )
+ */
+class SalesforceReference extends DataReferenceBase {
+
+  /**
+   * The Salesforce ID
+   *
+   * @var string
+   */
+  protected $sfid;
+
+  // @todo add useful things like object type and/or record type, and an actual reference to the data instead of just the ID
+  
+  /**
+   * {@inheritdoc}
+   */
+  public function getTargetDefinition() {
+    return array(
+      'type' => 'sobject',
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   * @todo retrieve the Salesforce Object record from cache instead of just the id.
+   */
+  public function getTarget() {
+    return $this->sfid;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getTargetIdentifier() {
+    return $this->sfid;
+  }
+
+}
\ No newline at end of file
diff --git a/src/SalesforceClient.php b/src/SalesforceClient.php
new file mode 100644
index 0000000..2498ee8
--- /dev/null
+++ b/src/SalesforceClient.php
@@ -0,0 +1,712 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\salesforce\SalesforceClient.
+ */
+
+namespace Drupal\salesforce;
+
+use Drupal\Core\Cache\CacheBackendInterface;
+use Drupal\Core\Config\ConfigFactory;
+use Drupal\Core\Routing\UrlGeneratorInterface;
+use Drupal\Core\Url;
+use Drupal\Component\Utility\Unicode;
+use Drupal\Component\Utility\UrlHelper;
+use Drupal\salesforce\SalesforceSelectQuery;
+#use Guzzle\Http\Exception\RequestException;
+use Guzzle\Http\ClientInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+use Symfony\Component\HttpFoundation\RedirectResponse;
+use GuzzleHttp\Client;
+use GuzzleHttp\Exception\RequestException;
+
+use Drupal\Component\Serialization\Json;
+
+/**
+ * Objects, properties, and methods to communicate with the Salesforce REST API.
+ */
+class SalesforceClient {
+
+  public $response;
+  protected $httpClient;
+  protected $configFactory;
+  protected $urlGenerator;
+  private $config;
+  private $configEditable;
+
+  /**
+   * Constructor which initializes the consumer.
+   * @param \Drupal\Core\Http\Client $http_client
+   *   The config factory
+   * @param \Guzzle\Http\ClientInterface $http_client
+   *   The config factory
+   */
+  public function __construct(Client $http_client, ConfigFactory $config_factory, UrlGeneratorInterface $url_generator) {
+    $this->configFactory = $config_factory;
+    $this->httpClient = $http_client;
+    $this->urlGenerator = $url_generator;
+    $this->config = $this->configFactory->get('salesforce.settings');
+    $this->configEditable = $this->configFactory->getEditable('salesforce.settings');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static(
+      $container->get('http_default_client'),
+      $container->get('config.factory')
+    );
+  }
+
+  /**
+   * Determine if this SF instance is fully configured.
+   *
+   * @TODO: Consider making a test API call.
+   */
+  public function isAuthorized() {
+    return $this->getConsumerKey() && $this->getConsumerSecret() && $this->getRefreshToken();
+  }
+
+  /**
+   * Make a call to the Salesforce REST API.
+   *
+   * @param string $path
+   *   Path to resource.
+   * @param array $params
+   *   Parameters to provide.
+   * @param string $method
+   *   Method to initiate the call, such as GET or POST.  Defaults to GET.
+   *
+   * @return mixed
+   *   The requested response.
+   *
+   * @throws SalesforceException
+   */
+  public function apiCall($path, $params = array(), $method = 'GET') {
+
+    if (!$this->getAccessToken()) {
+      $this->refreshToken();
+    }
+
+    try {
+      $this->response = $this->apiHttpRequest($path, $params, $method);
+    }
+    catch (RequestException $e) {
+      // A RequestException gets thrown if the response has any error status.
+      $this->response = $e->getResponse();
+    }
+
+    if (!is_object($this->response)) {
+      throw new SalesforceException('Unknown error occurred during API call');
+    }
+
+    switch ($this->response->getStatusCode()) {
+      case 401:
+        // The session ID or OAuth token used has expired or is invalid: refresh
+        // token. If refreshToken() throws an exception, or if apiHttpRequest()
+        // throws anything but a RequestException, let it bubble up.
+        $this->refreshToken();
+        try {
+          $this->response = $this->apiHttpRequest($path, $params, $method);
+        }
+        catch (RequestException $e) {
+          $this->response = $e->getRequest()->getResponse();
+          throw new SalesforceException($this->response->getReasonPhrase(), $this->response->getStatusCode());
+        }
+        break;
+      case 200:
+      case 201:
+      case 204:
+        // All clear.
+        break;
+
+      default:
+        // We have problem and no specific Salesforce error provided.
+        if (empty($this->response)) {
+          throw new SalesforceException('Unknown error occurred during API call');
+        }
+    }
+
+    try {
+      $data = Json::decode($this->response->getBody()->getContents());
+    }
+    catch (\RuntimeException $e) {
+      throw new SalesforceException('Unable to parse API response.');
+    }
+
+    if (!empty($data[0]) && count($data) == 1) {
+      $data = $data[0];
+    }
+
+    if (isset($data['error'])) {
+      throw new SalesforceException($data['error_description'], $data['error']);
+    }
+
+    if (!empty($data['errorCode'])) {
+      throw new SalesforceException($data['message'], $this->response->getStatusCode());
+    }
+
+    return $data;
+  }
+
+  /**
+   * Private helper to issue an SF API request.
+   *
+   * @param string $path
+   *   Path to resource.
+   * @param array $params
+   *   Parameters to provide.
+   * @param string $method
+   *   Method to initiate the call, such as GET or POST.  Defaults to GET.
+   *
+   * @return object
+   *   The requested data.
+   */
+  protected function apiHttpRequest($path, $params, $method) {
+    if (!$this->getAccessToken()) {
+      throw new SalesforceException('Missing OAuth Token');
+    }
+    $url = $this->getApiEndPoint() . $path;
+    $headers = array(
+      'Authorization' => 'OAuth ' . $this->getAccessToken(),
+      'Content-type' => 'application/json',
+    );
+    $data = NULL;
+    if (!empty($params)) {
+      // @todo: convert this into Dependency Injection
+      $data =  Json::encode($params);
+    }
+    return $this->httpRequest($url, $data, $headers, $method);
+  }
+
+  /**
+   * Make the HTTP request. Wrapper around drupal_http_request().
+   *
+   * @param string $url
+   *   Path to make request from.
+   * @param array $data
+   *   The request body.
+   * @param array $headers
+   *   Request headers to send as name => value.
+   * @param string $method
+   *   Method to initiate the call, such as GET or POST.  Defaults to GET.
+   *
+   * @throws RequestException
+   *
+   * @return object
+   *   Salesforce response object.
+   */
+  protected function httpRequest($url, $data = NULL, $headers = array(), $method = 'GET') {
+    // Build the request, including path and headers. Internal use.
+
+    $request = $this->httpClient->$method($url, array('headers' => $headers, 'body' => $data));
+    return $request;
+  }
+
+  /**
+   * Get the API end point for a given type of the API.
+   *
+   * @param string $api_type
+   *   E.g., rest, partner, enterprise.
+   *
+   * @return string
+   *   Complete URL endpoint for API access.
+   */
+  public function getApiEndPoint($api_type = 'rest') {
+    $url = &drupal_static(__FUNCTION__ . $api_type);
+    if (!isset($url)) {
+      $identity = $this->getIdentity();
+      if (is_string($identity)) {
+        $url = $identity;
+      }
+      elseif (isset($identity['urls'][$api_type])) {
+        $url = $identity['urls'][$api_type];
+      }
+      $url = str_replace('{version}', $this->config->get('rest_api_version.version'), $url);
+    }
+    return $url;
+  }
+
+  public function getConsumerKey() {
+    return $this->config->get('consumer_key');
+  }
+
+  public function setConsumerKey($value) {
+    return $this->configEditable->set('consumer_key', $value)->save();
+  }
+
+  public function getConsumerSecret() {
+    return $this->config->get('consumer_secret');
+  }
+
+  public function setConsumerSecret($value) {
+    return $this->configEditable->set('consumer_secret', $value)->save();
+  }
+  /**
+   * Get the SF instance URL. Useful for linking to objects.
+   */
+  public function getInstanceUrl() {
+    return $this->config->get('instance_url');
+  }
+
+  /**
+   * Set the SF instanc URL.
+   *
+   * @param string $url
+   *   URL to set.
+   */
+  protected function setInstanceUrl($url) {
+    $this->configEditable->set('instance_url', $url)->save();
+  }
+
+  /**
+   * Get the access token.
+   */
+  public function getAccessToken() {
+    $access_token = $this->config->get('access_token');
+    return isset($access_token) && Unicode::strlen($access_token) !== 0 ? $access_token : FALSE;
+  }
+
+  /**
+   * Set the access token.
+   *
+   * It is stored in session.
+   *
+   * @param string $token
+   *   Access token from Salesforce.
+   */
+  protected function setAccessToken($token) {
+    // @todo There is probably a better way to do this in D8.
+    // Why not put it in settings?
+    $this->configEditable->set('access_token', $token)->save();
+  }
+
+  /**
+   * Get refresh token.
+   */
+  protected function getRefreshToken() {
+    return $this->config->get('refresh_token');
+  }
+
+  /**
+   * Set refresh token.
+   *
+   * @param string $token
+   *   Refresh token from Salesforce.
+   */
+  protected function setRefreshToken($token) {
+    $this->configEditable->set('refresh_token', $token)->save();
+  }
+
+  /**
+   * Refresh access token based on the refresh token. Updates session variable.
+   *
+   * @throws SalesforceException
+   */
+  protected function refreshToken() {
+    $refresh_token = $this->getRefreshToken();
+    if (empty($refresh_token)) {
+      throw new SalesforceException(t('There is no refresh token.'));
+    }
+
+    $data = UrlHelper::buildQuery(array(
+      'grant_type' => 'refresh_token',
+      'refresh_token' => urldecode($refresh_token),
+      'client_id' => $this->getConsumerKey(),
+      'client_secret' => $this->getConsumerSecret(),
+    ));
+
+    $url = $this->config->get('login_url') . '/services/oauth2/token';
+    $headers = array(
+      // This is an undocumented requirement on Salesforce's end.
+      'Content-Type' => 'application/x-www-form-urlencoded',
+    );
+    $response = $this->httpRequest($url, $data, $headers, 'POST');
+
+    try {
+      $response = $this->httpRequest($url, $data, $headers, 'POST');
+    }
+    catch (RequestException $e) {
+      throw new SalesforceException(t('Unable to get a Salesforce access token.'), $e->getCode());
+    }
+
+    //if ($response->isError()) {
+    //  // @TODO: Deal with error better.
+    //  throw new SalesforceException(t('Unable to get a Salesforce access// token.'), $response->getStatusCode());
+    //}
+
+    try {
+      $data = Json::decode($response->getBody()->getContents());
+    }
+    catch (\RuntimeException $e) {
+      throw new SalesforceException($e->getMessage(), $e->getCode());
+    }
+
+    if (isset($data['error'])) {
+      throw new SalesforceException($data['error_description'], $data['error']);
+    }
+
+    $this->setAccessToken($data['access_token']);
+    $this->setIdentity($data['id']);
+    $this->configEditable
+      ->set('instance_url', $data['instance_url'])
+      ->save();
+  }
+
+  /**
+   * Retrieve and store the Salesforce identity given an ID url.
+   *
+   * @param string $id
+   *   Identity URL.
+   *
+   * @throws SalesforceException
+   */
+  protected function setIdentity($id) {
+    $headers = array(
+      'Authorization' => 'OAuth ' . $this->getAccessToken(),
+      'Content-type' => 'application/json',
+    );
+    $response = $this->httpRequest($id, NULL, $headers);
+    // @FIXME: isError doesn't exist on the Response Guzzle object.
+    if ($response->getStatusCode() != 200) {
+    //if ($response->isError()) {
+      throw new SalesforceException(t('Unable to access identity service.'), $response->getStatusCode());
+    }
+
+    try {
+      $data = Json::decode($response->getBody()->getContents());
+    }
+    catch (\RuntimeException $e) {
+      throw new SalesforceException($e->getMessage(), $e->getCode());
+    }
+
+    $this->configEditable->set('identity', $data)->save();
+  }
+
+  /**
+   * Return the Salesforce identity, which is stored in a variable.
+   *
+   * @return array
+   *   Returns FALSE is no identity has been stored.
+   */
+  public function getIdentity() {
+    return $this->config->get('identity');
+  }
+
+  /**
+   * OAuth step 1: Redirect to Salesforce and request and authorization code.
+   */
+  public function getAuthorizationCode() {
+    $path = $this->config->get('login_url') . '/services/oauth2/authorize';
+
+    $query = array(
+      'redirect_uri' => $this->redirectUrl(),
+      'response_type' => 'code',
+      'client_id' => $this->getConsumerKey(),
+    );
+
+    $response = new RedirectResponse(
+      Url::fromUri($path, array('query' => $query, 'absolute' => TRUE))->toUriString()
+    );
+    $response->send();
+  }
+
+  /**
+   * OAuth step 2: Exchange an authorization code for an access token.
+   *
+   * @param string $code
+   *   Code from Salesforce.
+   */
+  public function requestToken($code) {
+    $data = urldecode(UrlHelper::buildQuery(array(
+      'code' => $code,
+      'grant_type' => 'authorization_code',
+      'client_id' => $this->getConsumerKey(),
+      'client_secret' => $this->getConsumerSecret(),
+      'redirect_uri' => $this->redirectUrl(),
+    )));
+    $url = $this->config->get('login_url') . '/services/oauth2/token';
+    $headers = array(
+      // This is an undocumented requirement on SF's end.
+      'Content-Type' => 'application/x-www-form-urlencoded',
+    );
+
+    $response = $this->httpRequest($url, $data, $headers, 'POST');
+
+    // @FIXME: isError doesn't exist on the Response Guzzle object.
+    if ($response->getStatusCode() != 200) {
+    //if ($response->isError()) {
+      throw new SalesforceException($response->getReasonPhrase(), $response->getStatusCode());
+    }
+
+    try {
+      $data = Json::decode($response->getBody()->getContents());
+    }
+    catch (\RuntimeException $e) {
+      throw new SalesforceException($e->getMessage(), $e->getCode());
+    }
+
+
+    $this->setAccessToken($data['access_token']);
+    $this->setIdentity($data['id']);
+    $this->configEditable
+      ->set('refresh_token', $data['refresh_token'])
+      ->set('instance_url', $data['instance_url'])
+      ->save();
+  }
+
+  /**
+   * Helper to build the redirect URL for OAUTH workflow.
+   *
+   * @return string
+   *   Redirect URL.
+   */
+  protected function redirectUrl() {
+    return \Drupal::url('salesforce.oauth_callback', array(), array(
+      'absolute' => TRUE,
+      'https' => TRUE,
+    ));
+  }
+
+  /**
+   * @defgroup salesforce_apicalls Wrapper calls around core apiCall()
+   */
+
+  /**
+   * Available objects and their metadata for your organization's data.
+   *
+   * @param array $conditions
+   *   Associative array of filters to apply to the returned objects. Filters
+   *   are applied after the list is returned from Salesforce.
+   * @param bool $reset
+   *   Whether to reset the cache and retrieve a fresh version from Salesforce.
+   *
+   * @return array
+   *   Available objects and metadata.
+   *
+   * @addtogroup salesforce_apicalls
+   */
+  public function objects($conditions = array('updateable' => TRUE), $reset = FALSE) {
+    $cache = cache()->get('salesforce:objects');
+    // Force the recreation of the cache when it is older than 5 minutes.
+    if ($cache && REQUEST_TIME < ($cache->created + 300) && !$reset) {
+      $result = $cache->data;
+    }
+    else {
+      $result = $this->apiCall('sobjects');
+
+      // Allow the cache to clear at any time by not setting an expire time.
+      // CACHE_TEMPORARY has been removed. Using 'content' tag to replicate
+      // old functionality.
+      // @see https://drupal.org/node/1534648
+      cache()->set('salesforce:objects', $result, CacheBackendInterface::CACHE_PERMANENT, array('salesforce' => TRUE, 'content' => TRUE));
+    }
+
+    if (!empty($conditions)) {
+      foreach ($result['sobjects'] as $key => $object) {
+        foreach ($conditions as $condition => $value) {
+          if (!$object[$condition] == $value) {
+            unset($result['sobjects'][$key]);
+          }
+        }
+      }
+    }
+
+    return $result['sobjects'];
+  }
+
+  /**
+   * Use SOQL to get objects based on query string.
+   *
+   * @param SalesforceSelectQuery $query
+   *   The constructed SOQL query.
+   *
+   * @return array
+   *   Array of Salesforce objects that match the query.
+   *
+   * @addtogroup salesforce_apicalls
+   */
+  public function query(SalesforceSelectQuery $query) {
+    //$this->moduleHander->alter('salesforce_query', $query);
+    // Casting $query as a string calls SalesforceSelectQuery::__toString().
+
+    $result = $this->apiCall('query?q=' . (string) $query);
+    return $result;
+  }
+
+  /**
+   * Retreieve all the metadata for an object.
+   *
+   * @param string $name
+   *   Object type name, E.g., Contact, Account, etc.
+   * @param bool $reset
+   *   Whether to reset the cache and retrieve a fresh version from Salesforce.
+   *
+   * @return array
+   *   All the metadata for an object, including information about each field,
+   *   URLs, and child relationships.
+   *
+   * @addtogroup salesforce_apicalls
+   */
+  public function objectDescribe($name, $reset = FALSE) {
+    if (empty($name)) {
+      return array();
+    }
+    $cache = cache()->get('salesforce:object:' . $name);
+    // Force the recreation of the cache when it is older than 5 minutes.
+    if ($cache && REQUEST_TIME < ($cache->created + 300) && !$reset) {
+      return $cache->data;
+    }
+    else {
+      $object = $this->apiCall("sobjects/{$name}/describe");
+      // Allow the cache to clear at any time by not setting an expire time.
+      // CACHE_TEMPORARY has been removed. Using 'content' tag to replicate
+      // old functionality. @see https://drupal.org/node/1534648
+      cache()->set('salesforce:object:' . $name, $object, CacheBackendInterface::CACHE_PERMANENT, array('salesforce' => TRUE, 'content' => TRUE));
+      return $object;
+    }
+  }
+
+  /**
+   * Create a new object of the given type.
+   *
+   * @param string $name
+   *   Object type name, E.g., Contact, Account, etc.
+   * @param array $params
+   *   Values of the fields to set for the object.
+   *
+   * @return array
+   *   "id" : "001D000000IqhSLIAZ",
+   *   "errors" : [ ],
+   *   "success" : true
+   *
+   * @addtogroup salesforce_apicalls
+   */
+  public function objectCreate($name, $params) {
+    return $this->apiCall("sobjects/{$name}", $params, 'POST');
+  }
+
+  /**
+   * Create new records or update existing records.
+   *
+   * The new records or updated records are based on the value of the specified
+   * field.  If the value is not unique, REST API returns a 300 response with
+   * the list of matching records.
+   *
+   * @param string $name
+   *   Object type name, E.g., Contact, Account.
+   * @param string $key
+   *   The field to check if this record should be created or updated.
+   * @param string $value
+   *   The value for this record of the field specified for $key.
+   * @param array $params
+   *   Values of the fields to set for the object.
+   *
+   * @return array
+   *   successful create:
+   *     "id" : "00190000001pPvHAAU",
+   *     "errors" : [ ],
+   *     "success" : true
+   *   error:
+   *     "message" : "The requested resource does not exist"
+   *     "errorCode" : "NOT_FOUND"
+   *
+   * @addtogroup salesforce_apicalls
+   */
+  public function objectUpsert($name, $key, $value, $params) {
+    // If key is set, remove from $params to avoid UPSERT errors.
+    if (isset($params[$key])) {
+      unset($params[$key]);
+    }
+    // @todo handle "duplicate external id" response
+    // @see https://drupal.org/node/2140417
+    $data = $this->apiCall("sobjects/{$name}/{$key}/{$value}", $params, 'PATCH');
+    return $data;
+  }
+
+  /**
+   * Update an existing object.
+   *
+   * @param string $name
+   *   Object type name, E.g., Contact, Account.
+   * @param string $id
+   *   Salesforce id of the object.
+   * @param array $params
+   *   Values of the fields to set for the object.
+   *
+   * @return null
+   *
+   * @addtogroup salesforce_apicalls
+   */
+  public function objectUpdate($name, $id, $params) {
+    $this->apiCall("sobjects/{$name}/{$id}", $params, 'PATCH');
+  }
+
+  /**
+   * Return a full loaded Salesforce object.
+   *
+   * @param string $name
+   *   Object type name, E.g., Contact, Account.
+   * @param string $id
+   *   Salesforce id of the object.
+   *
+   * @return array
+   *   Object of the requested Salesforce object.
+   *
+   * @addtogroup salesforce_apicalls
+   */
+  public function objectRead($name, $id) {
+    return $this->apiCall("sobjects/{$name}/{$id}", array(), 'GET');
+  }
+
+  /**
+   * Return a full loaded Salesforce object given an external key
+   *
+   * @param string $name
+   *   Object type name, e.g. Contact, Account
+   * @param string $key_field
+   *   External key field name, e.g. External_ID__c
+   * @param string $key_value
+   *   External key value, e.g. $node->nid, $user->uid
+   *
+   * @return array
+   *   Object of the requested Salesforce object
+   *
+   * @addtogroup salesforce_apicalls
+   */
+  public function objectReadKey($name, $key_field, $key_value) {
+    return $this->apiCall("sobjects/{$naem}/{$key_field}/{$key_value}", 'GET');
+  }
+
+  /**
+   * Delete a Salesforce object.
+   *
+   * @param string $name
+   *   Object type name, E.g., Contact, Account.
+   * @param string $id
+   *   Salesforce id of the object.
+   *
+   * @addtogroup salesforce_apicalls
+   */
+  public function objectDelete($name, $id) {
+    $this->apiCall("sobjects/{$name}/{$id}", array(), 'DELETE');
+  }
+
+  /**
+   * Return a list of available resources for the configured API version.
+   *
+   * @return array
+   *   Associative array keyed by name with a URI value.
+   *
+   * @addtogroup salesforce_apicalls
+   */
+  public function listResources() {
+    $resources = $this->apiCall('');
+    foreach ($resources as $key => $path) {
+      $items[$key] = $path;
+    }
+    return $items;
+  }
+}
diff --git a/src/SalesforceException.php b/src/SalesforceException.php
new file mode 100644
index 0000000..564572a
--- /dev/null
+++ b/src/SalesforceException.php
@@ -0,0 +1,14 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\salesforce\SalesforceException.
+ */
+
+namespace Drupal\salesforce;
+
+use Symfony\Component\Serializer\Exception\Exception;
+
+class SalesforceException extends \RuntimeException implements Exception {
+
+}
diff --git a/src/SalesforceSelectQuery.php b/src/SalesforceSelectQuery.php
new file mode 100644
index 0000000..a18a485
--- /dev/null
+++ b/src/SalesforceSelectQuery.php
@@ -0,0 +1,95 @@
+<?php
+/**
+ * @file
+ * Class representing a Salesforce SELECT SOQL query.
+ */
+
+namespace Drupal\salesforce;
+
+class SalesforceSelectQuery {
+
+  public $fields = array();
+  public $order = array();
+  public $objectType;
+  public $limit;
+  public $conditions = array();
+
+  /**
+   * Constructor which sets the query object type.
+   *
+   * @param string $object_type
+   *   Salesforce object type to query.
+   */
+  public function __construct($object_type = '') {
+    $this->objectType = $object_type;
+  }
+
+  /**
+   * Add a condition to the query.
+   *
+   * @param string $field
+   *   Field name.
+   * @param mixed $value
+   *   Condition value. If an array, it will be split into quote enclosed
+   *   strings separated by commas inside of parenthesis. Note that the caller
+   *   must enclose the value in quotes as needed by the SF API.
+   * @param string $operator
+   *   Conditional operator. One of '=', '!=', '<', '>', 'LIKE, 'IN', 'NOT IN'.
+   */
+  public function addCondition($field, $value, $operator = '=') {
+    if (is_array($value)) {
+      $value = "('" . implode("','", $value) . "')";
+
+      // Set operator to IN if wasn't already changed from the default.
+      if ($operator == '=') {
+        $operator = 'IN';
+      }
+    }
+
+    $this->conditions[] = array(
+      'field' => $field,
+      'operator' => $operator,
+      'value' => $value,
+    );
+  }
+
+  /**
+   * Implements PHP's magic toString().
+   *
+   * Function to convert the query to a string to pass to the SF API.
+   *
+   * @return string
+   *   SOQL query ready to be executed the SF API.
+   */
+   // @codingStandardsIgnoreStart
+  public function __toString() {
+
+    $query = 'SELECT+';
+    $query .= implode(',+', $this->fields);
+    $query .= "+FROM+" . $this->objectType;
+
+    if (count($this->conditions) > 0) {
+      $where = array();
+      foreach ($this->conditions as $condition) {
+        $where[] = implode('+', $condition);
+      }
+      $query .= '+WHERE+' . implode('+AND+', $where);
+    }
+
+    if ($this->order) {
+      $query .= "+ORDER BY+";
+      $fields = array();
+      foreach ($this->order as $field => $direction) {
+        $fields[] = $field . ' ' . $direction;
+      }
+      $query .= implode(',+', $fields);
+    }
+
+    if ($this->limit) {
+      $query .= "+LIMIT+" . (int) $this->limit;
+    }
+
+    return $query;
+  }
+  // @codingStandardsIgnoreEnd
+}
-- 
2.5.4 (Apple Git-61)

