PageRenderTime 57ms CodeModel.GetById 26ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/Houston/Controllers/Salesforce/SalesForceClient.php

https://github.com/gotham-city-drupal/Houston
PHP | 523 lines | 314 code | 53 blank | 156 comment | 37 complexity | 210723ce6ad72a73e0759499d6fdf8f9 MD5 | raw file
  1. <?php
  2. require_once 'Houston/Controllers/Controller.php';
  3. require_once 'Houston/Controllers/Salesforce/phptoolkit/soapclient/SforceEnterpriseClient.php';
  4. require_once 'Houston/Application.php';
  5. class Houston_Controllers_Salesforce_SalesForceClient extends SforceEnterpriseClient implements Houston_Controllers_Controller_Interface {
  6. /**
  7. * Base table for this object.
  8. */
  9. const BASE_TABLE = '.houston_salesforce_query_log';
  10. const VARIABLES_TABLE = '.houston_variables';
  11. /**
  12. * username
  13. *
  14. * @var string
  15. */
  16. private $username = '';
  17. /**
  18. * password
  19. *
  20. * @var string
  21. */
  22. private $password = '';
  23. /**
  24. * securityToken
  25. *
  26. * @var string
  27. */
  28. private $securityToken = '';
  29. /**
  30. * wsdlFilename
  31. *
  32. * @var string
  33. */
  34. private $wsdlFilename = '';
  35. /**
  36. * The type of data in salesforce that this object will write to.
  37. *
  38. * @var string
  39. */
  40. private $type = '';
  41. /**
  42. * instance
  43. *
  44. * @var mixed
  45. */
  46. protected static $instance;
  47. /**
  48. * Whether or not we have a connection.
  49. *
  50. * NULL --> we haven't tried.
  51. * TRUE --> we tried, it was ok
  52. * FALSE --> we tried and failed
  53. */
  54. private $connected = NULL;
  55. /**
  56. *
  57. */
  58. private $db = NULL;
  59. /**
  60. * The fieldmap for this particular controller.
  61. */
  62. private $fieldMap = NULL;
  63. /**
  64. * getInstance
  65. *
  66. * @return SalesForceClient
  67. */
  68. public static function getInstance() {
  69. if (is_null(self::$instance)) {
  70. self::$instance = new self(array('db' => Zend_Registry::get('drupal_db'), 'wsdlFilename' => Zend_Registry::get('wsdlFilename')));
  71. }
  72. return self::$instance;
  73. }
  74. /**
  75. * Get the external object type for this particular connection.
  76. */
  77. public function getObjectType() {
  78. return $this->type;
  79. }
  80. /**
  81. * Save
  82. */
  83. public function save(stdClass $data) {
  84. $result = array('status' => FALSE);
  85. if (!isset($data->Id) || !$data->Id) {
  86. $data->Id = '';
  87. unset($data->Id);
  88. $response = $this->create(array($data));
  89. }
  90. else {
  91. $response = $this->update(array($data));
  92. }
  93. // Determine data to return.
  94. if (is_object($response) && isset($response->success) && $response->success) {
  95. $result['data'] = array('Id' => $response->id);
  96. $result['status'] = TRUE;
  97. }
  98. else if (is_object($response)) {
  99. $result['error'] = $response;
  100. }
  101. else {
  102. $result['error'] = 'NO_SALESFORCE_CONNECTION';
  103. }
  104. return $result;
  105. }
  106. /**
  107. * Load a single record.
  108. *
  109. * @return
  110. * An error array.
  111. */
  112. public function load(stdClass $data) {
  113. $result = array();
  114. if (!isset($data->Id)) {
  115. // Success is true, since there is no id to load.
  116. return array(
  117. 'status' => TRUE,
  118. 'message' => 'No ID specified',
  119. );
  120. }
  121. $fields = array();
  122. foreach ($data as $name => $value) {
  123. $fields[] = $name;
  124. }
  125. $response = $this->retrieve(implode($fields, ', '), $this->type, array($data->Id));
  126. if (is_object($response)) {
  127. $result = array(
  128. 'status' => TRUE,
  129. 'data' => $response,
  130. 'result' => $response,
  131. );
  132. }
  133. else {
  134. $result = array(
  135. 'status' => FALSE,
  136. 'message' => 'Data not found in salesforce',
  137. );
  138. }
  139. return $result;
  140. }
  141. public function create($data) {
  142. $type = $this->type;
  143. // TODO: decide if we're going to use the Zend debugLog and move this
  144. // into the Controller object if necessary.
  145. if ($this->connect() && !$this->reachedMaxSalesForceApiHits()) {
  146. $this->debugLog("Creating SF object $type with " . print_r($data, TRUE));
  147. try {
  148. $result = parent::create($data, $type);
  149. //$this->logSaleforceQuery('create', $result);
  150. return $result;
  151. }
  152. catch (Exception $e) {
  153. $this->debugLog($e);
  154. $result = new StdClass;
  155. $result->error = $e;
  156. return $result;
  157. }
  158. }
  159. return FALSE;
  160. }
  161. public function update($data) {
  162. $type = $this->type;
  163. // Some fields can only be created, not updated.
  164. foreach($this->fieldMap as $field => $info) {
  165. if (isset($info['update']) && !$info['update']) {
  166. // We are only updating one at a time, so we only
  167. // worry about the first item in the array.
  168. unset($data[0]->{$info['field']});
  169. }
  170. }
  171. if ($this->connect() && !$this->reachedMaxSalesForceApiHits()) {
  172. try {
  173. $this->debugLog("Updating SF object $type with " . print_r($data, TRUE));
  174. $result = parent::update($data, $type);
  175. //$this->logSaleforceQuery('update', $result);
  176. return $result;
  177. }
  178. catch (Exception $e) {
  179. $this->debugLog($e);
  180. $result = new StdClass;
  181. $result->error = $e;
  182. return $result;
  183. }
  184. }
  185. return FALSE;
  186. }
  187. /**
  188. * This method is a thin wrapper around the method by the same name ont
  189. * the SforceEnterpriseClient class provided by the Salesforce library.
  190. */
  191. public function retrieve($fields, $type, $ids) {
  192. if ($this->connect() && !$this->reachedMaxSalesForceApiHits()) {
  193. $this->debugLog("Retrieving SF objects $type with " . print_r($ids, TRUE));
  194. try {
  195. $result = parent::retrieve($fields, $type, $ids);
  196. //$this->logSaleforceQuery('retrieve', $result);
  197. return $result;
  198. }
  199. catch (Exception $e) {
  200. $this->debugLog($e);
  201. }
  202. }
  203. return FALSE;
  204. }
  205. /**
  206. * Deletes one or more new individual objects to your organization's data.
  207. *
  208. * @param array $ids Array of fields
  209. * @return DeleteResult
  210. */
  211. public function delete(stdClass $data) {
  212. $result = array(
  213. 'status' => FALSE,
  214. 'data' => array()
  215. );
  216. $ids = array($data->Id);
  217. if ($this->connect() && !$this->reachedMaxSalesForceApiHits()) {
  218. $this->debugLog("Deleting SF objects $type with " . print_r($ids, TRUE));
  219. try {
  220. $response = parent::delete($ids);
  221. if ($response->success) {
  222. $result['status'] = TRUE;
  223. }
  224. else {
  225. $result['error'] = $response;
  226. }
  227. //$this->logSaleforceQuery('retrieve', $result);
  228. }
  229. catch (Exception $e) {
  230. $result['error'] = $e;
  231. $this->debugLog($e);
  232. }
  233. }
  234. return $result;
  235. }
  236. /**
  237. * Map data from the controller into the data object.
  238. *
  239. * @param $data
  240. */
  241. public function mapData($input, $salesforceFields = TRUE) {
  242. $data = new stdClass;
  243. $controllerMapping = $this->fieldMap;
  244. foreach ($controllerMapping as $localName => $fieldData) {
  245. if (isset($fieldData['load']) && !$fieldData['load']) {
  246. continue;
  247. }
  248. $controllerName = $fieldData['field'];
  249. if (isset($input->$controllerName)) {
  250. if ($salesforceFields) {
  251. $data->$controllerName = $input->$controllerName;
  252. }
  253. else {
  254. $data->$localName = $input->$controllerName;
  255. }
  256. }
  257. }
  258. return $data;
  259. }
  260. /**
  261. * __construct
  262. *
  263. * @return void
  264. */
  265. public function __construct(array $config = NULL) {
  266. // TODO: This is a bloody sloppy way of doing this.
  267. // We should really be passing around a config object here.
  268. /*
  269. $requiredConstants = array('HOUSTON_SFDC_WSDL', 'HOUSTON_SF_USER', 'HOUSTON_SF_PASS', 'HOUSTON_SF_TOKEN');
  270. foreach ($requiredConstants as $constantName) {
  271. if (!defined($constantName)) {
  272. throw new Exception("Missing required constant '$constantName'");
  273. }
  274. }
  275. */
  276. // TODO: test this approach!
  277. // Populate the fields
  278. foreach ($this as $name => $value) {
  279. if (isset($config[$name])) {
  280. $this->$name = $config[$name];
  281. }
  282. }
  283. if (!file_exists($this->wsdlFilename)) {
  284. throw new Exception("No wsdl file at '{$this->wsdlFilename}'");
  285. }
  286. if (!is_readable($this->wsdlFilename)) {
  287. throw new Exception("Can't read wsdl file at '{$this->wsdlFilename}'");
  288. }
  289. if ($config) {
  290. $this->processConfig($config);
  291. }
  292. }
  293. /**
  294. * connect
  295. *
  296. * @return void
  297. */
  298. public function connect() {
  299. // Find out if the connection should be enabled.
  300. $application = Zend_Registry::get('Houston_Application');
  301. $sfEnabled = $application->variableGet('salesforceConnection', TRUE);
  302. if ($this->connected === NULL && $sfEnabled) {
  303. try {
  304. $this->SforceEnterpriseClient();
  305. $this->createConnection($this->wsdlFilename);
  306. $this->connected = $this->login($this->username, $this->password . $this->securityToken);
  307. }
  308. catch (Exception $e) {
  309. $this->connected = FALSE;
  310. }
  311. }
  312. return $this->connected;
  313. }
  314. /**
  315. * processConfig
  316. *
  317. * @param array $config
  318. * @return void
  319. */
  320. public function processConfig(array $config) {
  321. if (isset($config['wsdlFilename'])) {
  322. $this->setWsdlFileName($config['wsdlFilename']);
  323. }
  324. if (isset($config['username'])) {
  325. $this->setUsername($config['username']);
  326. }
  327. if (isset($config['password'])) {
  328. $this->setPassword($config['password']);
  329. }
  330. if (isset($config['securityToken'])) {
  331. $this->setSecurityToken($config['securityToken']);
  332. }
  333. if (isset($config['db'])) {
  334. $this->db = $config['db'];
  335. }
  336. if (isset($config['fieldMap'])) {
  337. $this->fieldMap = $config['fieldMap'];
  338. }
  339. }
  340. /**
  341. * setWsdlFileName
  342. *
  343. * @param mixed $wsdlFilename
  344. * @return void
  345. */
  346. public function setWsdlFileName($wsdlFilename) {
  347. if (!file_exists($this->wsdlFilename)) {
  348. throw new Exception("File '{$this->wsdlFilename}' doesn't exist in '" . HOUSTON_BASE_DIR . "wsdl' directory");
  349. }
  350. }
  351. /**
  352. * setPassword
  353. *
  354. * @param mixed $password
  355. * @return void
  356. */
  357. public function setPassword($password) {
  358. $this->password = $password;
  359. }
  360. /**
  361. * setUsername
  362. *
  363. * @param mixed $username
  364. * @return void
  365. */
  366. public function setUsername($username) {
  367. $this->username = $username;
  368. }
  369. /**
  370. * setSecurityToken
  371. *
  372. * @param mixed $securityToken
  373. * @return void
  374. */
  375. public function setSecurityToken($securityToken) {
  376. $this->securityToken = $securityToken;
  377. }
  378. /**
  379. * Execute a SOQL query and return the result array or FALSE on failure
  380. */
  381. public function runSoqlQuery($soql) {
  382. if ($this->connect() && !$this->reachedMaxSalesForceApiHits()) {
  383. try {
  384. $result = $this->query("$soql");
  385. //$this->logSaleforceQuery('soql', $result);
  386. $this->debugLog("sent SOQL '$soql', got result:\n" . print_r($result, TRUE));
  387. return $result;
  388. }
  389. catch (SoapFault $e) {
  390. $this->debugLog($e);
  391. return FALSE;
  392. }
  393. }
  394. return FALSE;
  395. }
  396. /**
  397. * Check if the maximum number of salesforce api hits has been reached
  398. *
  399. * @return boolean
  400. */
  401. public function reachedMaxSalesForceApiHits() {
  402. // TODO: Update this so it doesn't always return FALSE
  403. return FALSE;
  404. $application = Zend_Registry::get('Houston_Application');
  405. $dailyQueries = $application->variableGet('salesforceQueries', 60000);
  406. $todate = $this->getSalesForceApiHits();
  407. if ($todate < $dailyQueries) {
  408. return FALSE;
  409. }
  410. return TRUE;
  411. }
  412. /**
  413. * Get the number of recorded salesforce api hits for the day
  414. *
  415. * @return int
  416. */
  417. public function getSalesForceApiHits() {
  418. $baseTable = self::BASE_TABLE;
  419. $sql = 'SELECT COUNT(*) AS num FROM ' . houston_legacy_DB . self::BASE_TABLE;
  420. $result = $this->db->fetchRow($sql);
  421. $number = $result->num;
  422. if ($number) {
  423. return $number;
  424. }
  425. return 0;
  426. }
  427. /**
  428. * Reset the number of API hits for the day
  429. *
  430. * @return void
  431. */
  432. public function resetSalesForceApiHits() {
  433. }
  434. /**
  435. * Log this Salesforce query to the db query log.
  436. */
  437. public function logSaleforceQuery($method, $result) {
  438. $row = array(
  439. 'method' => $method,
  440. 'response' => serialize($result),
  441. 'timestamp' => time(),
  442. );
  443. $this->db->insert(HOUSTON_DB . self::BASE_TABLE, $row);
  444. }
  445. /**
  446. * Deletes all entries from the Salesforce query log.
  447. * This operation should be performed daily by a cron job.
  448. */
  449. public function clearSalesforceQueryLog() {
  450. $this->db->query('DELETE FROM ' . HOUSTON_DB . self::BASE_TABLE);
  451. }
  452. /**
  453. * Try to log a debug message.
  454. *
  455. * @param mixed $message
  456. */
  457. public function debugLog($message) {
  458. try {
  459. Zend_Registry::get('debug_log')->log($message, Zend_Log::INFO);
  460. }
  461. catch (Exception $e) { }
  462. }
  463. }