source: extensions/oAuth/include/hybridauth/Hybrid/thirdparty/LinkedIn/LinkedIn.php @ 20293

Last change on this file since 20293 was 20293, checked in by mistic100, 11 years ago

first commit of oAuth plugin, still in developpement

File size: 90.3 KB
Line 
1<?php
2// http://code.google.com/p/simple-linkedinphp/
3// 3.2.0 - November 29, 2011
4// hacked into the code to handel new scope (r_basicprofile+r_emailaddress) - until Paul update linkedinphp library!
5
6/**
7 * This file defines the 'LinkedIn' class. This class is designed to be a
8 * simple, stand-alone implementation of the LinkedIn API functions.
9 *
10 * COPYRIGHT:
11 *   
12 * Copyright (C) 2011, fiftyMission Inc.
13 *
14 * Permission is hereby granted, free of charge, to any person obtaining a
15 * copy of this software and associated documentation files (the "Software"),
16 * to deal in the Software without restriction, including without limitation
17 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
18 * and/or sell copies of the Software, and to permit persons to whom the
19 * Software is furnished to do so, subject to the following conditions:
20 *
21 * The above copyright notice and this permission notice shall be included in
22 * all copies or substantial portions of the Software. 
23 *
24 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
25 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
26 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
27 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
28 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
29 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
30 * IN THE SOFTWARE. 
31 *
32 * SOURCE CODE LOCATION:
33 *
34 * http://code.google.com/p/simple-linkedinphp/
35 *   
36 * REQUIREMENTS:
37 *
38 * 1. You must have cURL installed on the server and available to PHP.
39 * 2. You must be running PHP 5+. 
40 * 
41 * QUICK START:
42 *
43 * There are two files needed to enable LinkedIn API functionality from PHP; the
44 * stand-alone OAuth library, and this LinkedIn class. The latest version of
45 * the stand-alone OAuth library can be found on Google Code:
46 *
47 * http://code.google.com/p/oauth/
48 *   
49 * Install these two files on your server in a location that is accessible to
50 * the scripts you wish to use them in. Make sure to change the file
51 * permissions such that your web server can read the files.
52 *
53 * Next, make sure the path to the OAuth library is correct (you can change this
54 * as needed, depending on your file organization scheme, etc).
55 *
56 * Finally, test the class by attempting to connect to LinkedIn using the
57 * associated demo.php page, also located at the Google Code location
58 * referenced above.                   
59 *   
60 * RESOURCES:
61 *   
62 * REST API Documentation: http://developer.linkedin.com/rest
63 *   
64 * @version 3.2.0 - November 8, 2011
65 * @author Paul Mennega <paul@fiftymission.net>
66 * @copyright Copyright 2011, fiftyMission Inc.
67 * @license http://www.opensource.org/licenses/mit-license.php The MIT License
68 */
69
70/**
71 * 'LinkedInException' class declaration.
72 * 
73 * This class extends the base 'Exception' class.
74 *
75 * @access public
76 * @package classpackage
77 */
78class LinkedInException extends Exception {}
79
80/**
81 * 'LinkedIn' class declaration.
82 * 
83 * This class provides generalized LinkedIn oauth functionality.
84 *
85 * @access public
86 * @package classpackage
87 */
88class LinkedIn {
89  // api/oauth settings
90  const _API_OAUTH_REALM             = 'http://api.linkedin.com';
91  const _API_OAUTH_VERSION           = '1.0';
92 
93  // the default response format from LinkedIn
94  const _DEFAULT_RESPONSE_FORMAT     = 'xml';
95   
96  // helper constants used to standardize LinkedIn <-> API communication.  See demo page for usage.
97  const _GET_RESPONSE                = 'lResponse';
98  const _GET_TYPE                    = 'lType';
99 
100  // Invitation API constants.
101  const _INV_SUBJECT                 = 'Invitation to connect';
102  const _INV_BODY_LENGTH             = 200;
103 
104  // API methods
105  const _METHOD_TOKENS               = 'POST';
106 
107  // Network API constants.
108  const _NETWORK_LENGTH              = 1000;
109  const _NETWORK_HTML                = '<a>';
110 
111  // response format type constants, see http://developer.linkedin.com/docs/DOC-1203
112  const _RESPONSE_JSON               = 'JSON';
113  const _RESPONSE_JSONP              = 'JSONP';
114  const _RESPONSE_XML                = 'XML';
115 
116  // Share API constants
117  const _SHARE_COMMENT_LENGTH        = 700;
118  const _SHARE_CONTENT_TITLE_LENGTH  = 200;
119  const _SHARE_CONTENT_DESC_LENGTH   = 400;
120 
121  // LinkedIn API end-points
122        const _URL_ACCESS                  = 'https://api.linkedin.com/uas/oauth/accessToken';
123        const _URL_API                     = 'https://api.linkedin.com';
124        const _URL_AUTH                    = 'https://www.linkedin.com/uas/oauth/authenticate?oauth_token=';
125        // const _URL_REQUEST                 = 'https://api.linkedin.com/uas/oauth/requestToken';
126        const _URL_REQUEST                 = 'https://api.linkedin.com/uas/oauth/requestToken?scope=r_basicprofile+r_emailaddress'; 
127        const _URL_REVOKE                  = 'https://api.linkedin.com/uas/oauth/invalidateToken';
128       
129        // Library version
130        const _VERSION                     = '3.2.0';
131 
132  // oauth properties
133  protected $callback;
134  protected $token                   = NULL;
135 
136  // application properties
137  protected $application_key, 
138            $application_secret;
139 
140  // the format of the data to return
141  protected $response_format         = self::_DEFAULT_RESPONSE_FORMAT;
142
143  // last request fields
144  public $last_request_headers, 
145         $last_request_url;
146
147        /**
148         * Create a LinkedIn object, used for OAuth-based authentication and
149         * communication with the LinkedIn API. 
150         *
151         * @param arr $config
152         *    The 'start-up' object properties:
153         *           - appKey       => The application's API key
154         *           - appSecret    => The application's secret key
155         *           - callbackUrl  => [OPTIONAL] the callback URL
156         *                       
157         * @return obj
158         *    A new LinkedIn object.     
159         */
160        public function __construct($config) {
161    if(!is_array($config)) {
162      // bad data passed
163                  throw new LinkedInException('LinkedIn->__construct(): bad data passed, $config must be of type array.');
164    }
165    $this->setApplicationKey($config['appKey']);
166          $this->setApplicationSecret($config['appSecret']);
167          $this->setCallbackUrl($config['callbackUrl']);
168        }
169       
170        /**
171   * The class destructor.
172   *
173   * Explicitly clears LinkedIn object from memory upon destruction.
174         */
175  public function __destruct() {
176    unset($this);
177        }
178       
179        /**
180         * Bookmark a job.
181         *
182         * Calling this method causes the current user to add a bookmark for the
183         * specified job:
184         *
185         *   http://developer.linkedin.com/docs/DOC-1323
186         *
187         * @param str $jid
188         *    Job ID you want to bookmark.
189         *               
190         * @return arr
191         *    Array containing retrieval success, LinkedIn response.
192         */
193        public function bookmarkJob($jid) {
194          // check passed data
195          if(!is_string($jid)) {
196            // bad data passed
197                  throw new LinkedInException('LinkedIn->bookmarkJob(): bad data passed, $jid must be of type string.');
198          }
199         
200          // construct and send the request
201          $query    = self::_URL_API . '/v1/people/~/job-bookmarks';
202          $response = $this->fetch('POST', $query, '<job-bookmark><job><id>' . trim($jid) . '</id></job></job-bookmark>');
203         
204          /**
205           * Check for successful request (a 201 response from LinkedIn server)
206           * per the documentation linked in method comments above.
207           */
208                return $this->checkResponse(201, $response);
209        }
210       
211        /**
212         * Get list of jobs you have bookmarked.
213         *
214         * Returns a list of jobs the current user has bookmarked, per:
215         *
216         *   http://developer.linkedin.com/docs/DOC-1323   
217         *     
218         * @return arr
219         *         Array containing retrieval success, LinkedIn response.
220         */
221        public function bookmarkedJobs() {     
222    // construct and send the request 
223          $query    = self::_URL_API . '/v1/people/~/job-bookmarks';
224          $response = $this->fetch('GET', $query);
225         
226          /**
227           * Check for successful request (a 200 response from LinkedIn server)
228           * per the documentation linked in method comments above.
229           */
230                return $this->checkResponse(200, $response);
231        }
232       
233        /**
234         * Custom addition to make code compatible with PHP 5.2
235         */
236        private function intWalker($value, $key) {
237        if(!is_int($value)) {
238                        throw new LinkedInException('LinkedIn->checkResponse(): $http_code_required must be an integer or an array of integer values');
239                }
240    }
241       
242        /**
243         * Used to check whether a response LinkedIn object has the required http_code or not and
244         * returns an appropriate LinkedIn object.
245         *
246         * @param var $http_code_required
247         *              The required http response from LinkedIn, passed in either as an integer,
248         *              or an array of integers representing the expected values.       
249         * @param arr $response
250         *    An array containing a LinkedIn response.
251         *
252         * @return boolean
253         *        TRUE or FALSE depending on if the passed LinkedIn response matches the expected response.
254         */
255        private function checkResponse($http_code_required, $response) {
256                // check passed data
257    if(is_array($http_code_required)) {
258                  array_walk($http_code_required, array($this, 'intWalker'));
259                } else {
260                  if(!is_int($http_code_required)) {
261                        throw new LinkedInException('LinkedIn->checkResponse(): $http_code_required must be an integer or an array of integer values');
262                } else {
263                  $http_code_required = array($http_code_required);
264                }
265                }
266                if(!is_array($response)) {
267                        throw new LinkedInException('LinkedIn->checkResponse(): $response must be an array');
268                }               
269               
270                // check for a match
271                if(in_array($response['info']['http_code'], $http_code_required)) {
272                  // response found
273                  $response['success'] = TRUE;
274                } else {
275                        // response not found
276                        $response['success'] = FALSE;
277                        $response['error']   = 'HTTP response from LinkedIn end-point was not code ' . implode(', ', $http_code_required);
278                }
279                return $response;
280        }
281       
282        /**
283         * Close a job.
284         *
285         * Calling this method causes the passed job to be closed, per:
286         *
287         *   http://developer.linkedin.com/docs/DOC-1151   
288         *
289         * @param str $jid
290         *    Job ID you want to close.
291         *             
292         * @return arr
293         *    Array containing retrieval success, LinkedIn response.
294         */
295        public function closeJob($jid) {
296          // check passed data
297          if(!is_string($jid)) {
298            // bad data passed
299                  throw new LinkedInException('LinkedIn->closeJob(): bad data passed, $jid must be of string value.');
300          }
301         
302          // construct and send the request
303          $query    = self::_URL_API . '/v1/jobs/partner-job-id=' . trim($jid);
304          $response = $this->fetch('DELETE', $query);
305         
306          /**
307           * Check for successful request (a 204 response from LinkedIn server)
308           * per the documentation linked in method comments above.
309           */
310          return $this->checkResponse(204, $response);
311        }
312       
313        /**
314         * Share comment posting method.
315         *
316         * Post a comment on an existing connections shared content. API details can
317         * be found here:
318         *
319         * http://developer.linkedin.com/docs/DOC-1043
320         *
321         * @param str $uid
322         *    The LinkedIn update ID.           
323         * @param str $comment
324         *    The share comment to be posted.
325         *               
326         * @return arr
327         *    Array containing retrieval success, LinkedIn response.             
328         */
329        public function comment($uid, $comment) {
330          // check passed data
331          if(!is_string($uid)) {
332            // bad data passed
333                  throw new LinkedInException('LinkedIn->comment(): bad data passed, $uid must be of type string.');
334          }
335    if(!is_string($comment)) {
336      // nothing/non-string passed, raise an exception
337                  throw new LinkedInException('LinkedIn->comment(): bad data passed, $comment must be a non-zero length string.');
338    }
339   
340    /**
341     * Share comment rules:
342     *
343     * 1) No HTML permitted.
344     * 2) Comment cannot be longer than 700 characters.     
345     */
346    $comment = substr(trim(htmlspecialchars(strip_tags($comment))), 0, self::_SHARE_COMMENT_LENGTH);
347                $data    = '<?xml version="1.0" encoding="UTF-8"?>
348                <update-comment>
349                                        <comment>' . $comment . '</comment>
350                                      </update-comment>';
351
352    // construct and send the request
353    $query    = self::_URL_API . '/v1/people/~/network/updates/key=' . $uid . '/update-comments';
354    $response = $this->fetch('POST', $query, $data);
355   
356    /**
357           * Check for successful request (a 201 response from LinkedIn server)
358           * per the documentation linked in method comments above.
359           */ 
360    return $this->checkResponse(201, $response);
361        }
362       
363        /**
364         * Share comment retrieval.
365         *     
366         * Return all comments associated with a given network update:
367         *       
368         *   http://developer.linkedin.com/docs/DOC-1043
369         *
370         * @param str $uid
371         *    The LinkedIn update ID.
372         *                       
373         * @return arr
374         *    Array containing retrieval success, LinkedIn response.                 
375         */
376        public function comments($uid) {
377          // check passed data
378          if(!is_string($uid)) {
379            // bad data passed
380                  throw new LinkedInException('LinkedIn->comments(): bad data passed, $uid must be of type string.');
381          }
382               
383                // construct and send the request
384    $query    = self::_URL_API . '/v1/people/~/network/updates/key=' . $uid . '/update-comments';
385    $response = $this->fetch('GET', $query);
386   
387        /**
388           * Check for successful request (a 200 response from LinkedIn server)
389           * per the documentation linked in method comments above.
390           */ 
391    return $this->checkResponse(200, $response);
392        }
393       
394        /**
395         * Company profile retrieval function.
396         *
397         * Takes a string of parameters as input and requests company profile data
398         * from the LinkedIn Company Profile API. See the official documentation for
399         * $options 'field selector' formatting:
400         *
401         *   http://developer.linkedin.com/docs/DOC-1014
402         *   http://developer.linkedin.com/docs/DOC-1259   
403         *
404         * @param str $options
405         *    Data retrieval options.   
406         * @param       bool $by_email
407         *    [OPTIONAL] Search by email domain?
408         *       
409         * @return arr
410         *    Array containing retrieval success, LinkedIn response.
411         */
412        public function company($options, $by_email = FALSE) {
413          // check passed data
414          if(!is_string($options)) {
415            // bad data passed
416                  throw new LinkedInException('LinkedIn->company(): bad data passed, $options must be of type string.');
417          }
418          if(!is_bool($by_email)) {
419            // bad data passed
420                  throw new LinkedInException('LinkedIn->company(): bad data passed, $by_email must be of type boolean.');
421          }
422         
423          // construct and send the request
424          $query    = self::_URL_API . '/v1/companies' . ($by_email ? '' : '/') . trim($options);
425          $response = $this->fetch('GET', $query);
426         
427          /**
428           * Check for successful request (a 200 response from LinkedIn server)
429           * per the documentation linked in method comments above.
430           */
431          return $this->checkResponse(200, $response);
432        }
433       
434  /**
435         * Company products and their associated recommendations.
436         *
437         * The product data type contains details about a company's product or
438         * service, including recommendations from LinkedIn members, and replies from
439         * company representatives.
440         *
441         *   http://developer.linkedin.com/docs/DOC-1327   
442         *
443         * @param str $cid
444         *    Company ID you want the producte for.     
445         * @param str $options
446         *    [OPTIONAL] Data retrieval options.
447         *             
448         * @return arr
449         *    Array containing retrieval success, LinkedIn response.
450         */
451        public function companyProducts($cid, $options = '') {
452          // check passed data
453          if(!is_string($cid)) {
454            // bad data passed
455                  throw new LinkedInException('LinkedIn->companyProducts(): bad data passed, $cid must be of type string.');
456          }
457          if(!is_string($options)) {
458            // bad data passed
459                  throw new LinkedInException('LinkedIn->companyProducts(): bad data passed, $options must be of type string.');
460          }
461         
462          // construct and send the request
463          $query    = self::_URL_API . '/v1/companies/' . trim($cid) . '/products' . trim($options);
464          $response = $this->fetch('GET', $query);
465         
466          /**
467           * Check for successful request (a 200 response from LinkedIn server)
468           * per the documentation linked in method comments above.
469           */
470          return $this->checkResponse(200, $response);
471        }
472       
473        /**
474         * Connection retrieval function.
475         *
476         * Takes a string of parameters as input and requests connection-related data
477         * from the Linkedin Connections API. See the official documentation for
478         * $options 'field selector' formatting:
479         *
480         *   http://developer.linkedin.com/docs/DOC-1014         
481         *
482         * @param str $options
483         *    [OPTIONAL] Data retrieval options.
484         *               
485         * @return arr
486         *    Array containing retrieval success, LinkedIn response.
487         */
488        public function connections($options = '~/connections') {
489          // check passed data
490          if(!is_string($options)) {
491            // bad data passed
492                  throw new LinkedInException('LinkedIn->connections(): bad data passed, $options must be of type string.');
493          }
494         
495          // construct and send the request
496          $query    = self::_URL_API . '/v1/people/' . trim($options);
497          $response = $this->fetch('GET', $query);
498         
499          /**
500           * Check for successful request (a 200 response from LinkedIn server)
501           * per the documentation linked in method comments above.
502           */
503          return $this->checkResponse(200, $response);
504        }
505       
506        /**
507         * This creates a post in the specified group with the specified title and specified summary.
508         *
509         *   http://developer.linkedin.com/documents/groups-api
510         *
511         * @param str $gid
512         *              The group id.
513         * @param str $title
514         *              The title of the post. This must be non-empty.
515         * @param str $summary
516         *              [OPTIONAL] The content or summary of the post. This can be empty.
517         *
518         * @return arr
519         *              Array containing retrieval success, LinkedIn response.
520         */
521        public function createPost($gid, $title, $summary = '') {
522                if(!is_string($gid)) {
523                        throw new LinkedInException('LinkedIn->createPost(): bad data passed, $gid must be of type string.');
524                }
525                if(!is_string($title) || empty($title)) {
526                        throw new LinkedInException('LinkedIn->createPost(): bad data passed, $title must be a non-empty string.');
527                }
528                if(!is_string($summary)) {
529                        throw new LinkedInException('LinkedIn->createPost(): bad data passed, $summary must be of type string.');
530                }
531               
532                // construct the XML
533                $data = '<?xml version="1.0" encoding="UTF-8"?>
534                                 <post>
535                                         <title>'. $title . '</title>
536                                         <summary>' . $summary . '</summary>
537                                 </post>';
538               
539                // construct and send the request
540                $query    = self::_URL_API . '/v1/groups/' . trim($gid) . '/posts';
541                $response = $this->fetch('POST', $query, $data);
542               
543          /**
544           * Check for successful request (a 201 response from LinkedIn server)
545           * per the documentation linked in method comments above.
546           */
547                return $this->checkResponse(201, $response);
548        }
549       
550        /**
551         * This deletes the specified post if you are the owner or moderator that post.
552         * Otherwise, it just flags the post as inappropriate.
553         *
554         * https://developer.linkedin.com/documents/groups-api
555         *
556         * @param str $pid
557         *              The post id.
558         *
559         * @return arr
560         *              Array containing retrieval success, LinkedIn response.
561         */
562        public function deletePost($pid) {
563                if(!is_string($pid)) {
564                        throw new LinkedInException('LinkedIn->deletePost(): bad data passed, $pid must be of type string');
565                }
566               
567                // construct and send the request
568                $query    = self::_URL_API . '/v1/posts/' . trim($pid);
569                $response = $this->fetch('DELETE', $query);
570               
571    /**
572     * Check for successful request (a 204 response from LinkedIn server)
573           * per the documentation linked in method comments above.
574           */
575                return $this->checkResponse(204, $response);
576        }
577       
578        /**
579         * Edit a job.
580         *
581         * Calling this method causes the passed job to be edited, with the passed
582         * XML instructing which fields to change, per:
583         *
584         *   http://developer.linkedin.com/docs/DOC-1154
585         *   http://developer.linkedin.com/docs/DOC-1142     
586         *
587         * @param str $jid
588         *    Job ID you want to renew.
589         * @param str $xml
590         *    The XML containing the job fields to edit.         
591         *             
592         * @return arr
593         *    Array containing retrieval success, LinkedIn response.
594         */
595        public function editJob($jid, $xml) {
596          // check passed data
597          if(!is_string($jid)) {
598            // bad data passed
599                  throw new LinkedInException('LinkedIn->editJob(): bad data passed, $jid must be of string value.');
600          }
601          if(is_string($xml)) {
602            $xml = trim(stripslashes($xml));
603          } else {
604            // bad data passed
605                  throw new LinkedInException('LinkedIn->editJob(): bad data passed, $xml must be of string value.');
606          }
607               
608          // construct and send the request
609          $query    = self::_URL_API . '/v1/jobs/partner-job-id=' . trim($jid);
610          $response = $this->fetch('PUT', $query, $xml);
611         
612          /**
613           * Check for successful request (a 200 response from LinkedIn server)
614           * per the documentation linked in method comments above.
615           */
616          return $this->checkResponse(200, $response);
617        }
618       
619        /**
620         * General data send/request method.
621         *
622         * @param str $method
623         *    The data communication method.     
624         * @param str $url
625         *    The Linkedin API endpoint to connect with.
626         * @param str $data
627         *    [OPTIONAL] The data to send to LinkedIn.
628         * @param arr $parameters
629         *    [OPTIONAL] Addition OAuth parameters to send to LinkedIn.
630         *       
631         * @return arr
632         *    Array containing:
633         *
634         *           array(
635         *             'info'      =>   Connection information,
636         *             'linkedin'  => LinkedIn response, 
637         *             'oauth'     => The OAuth request string that was sent to LinkedIn         
638         *           )   
639         */
640        protected function fetch($method, $url, $data = NULL, $parameters = array()) {
641          // check for cURL
642          if(!extension_loaded('curl')) {
643            // cURL not present
644      throw new LinkedInException('LinkedIn->fetch(): PHP cURL extension does not appear to be loaded/present.');
645          }
646         
647    try {
648            // generate OAuth values
649            $oauth_consumer  = new OAuthConsumer($this->getApplicationKey(), $this->getApplicationSecret(), $this->getCallbackUrl());
650            $oauth_token     = $this->getToken();
651            $oauth_token     = (!is_null($oauth_token)) ? new OAuthToken($oauth_token['oauth_token'], $oauth_token['oauth_token_secret']) : NULL;
652      $defaults        = array(
653        'oauth_version' => self::_API_OAUTH_VERSION
654      );
655            $parameters    = array_merge($defaults, $parameters);
656           
657            // generate OAuth request
658                $oauth_req = OAuthRequest::from_consumer_and_token($oauth_consumer, $oauth_token, $method, $url, $parameters);
659      $oauth_req->sign_request(new OAuthSignatureMethod_HMAC_SHA1(), $oauth_consumer, $oauth_token);
660     
661      // start cURL, checking for a successful initiation
662      if(!$handle = curl_init()) {
663         // cURL failed to start
664        throw new LinkedInException('LinkedIn->fetch(): cURL did not initialize properly.');
665      }
666     
667      // set cURL options, based on parameters passed
668            curl_setopt($handle, CURLOPT_CUSTOMREQUEST, $method);
669      curl_setopt($handle, CURLOPT_RETURNTRANSFER, TRUE);
670      curl_setopt($handle, CURLOPT_SSL_VERIFYPEER, FALSE);
671      curl_setopt($handle, CURLOPT_URL, $url);
672      curl_setopt($handle, CURLOPT_VERBOSE, FALSE);
673     
674      // configure the header we are sending to LinkedIn - http://developer.linkedin.com/docs/DOC-1203
675      $header = array($oauth_req->to_header(self::_API_OAUTH_REALM));
676      if(is_null($data)) {
677        // not sending data, identify the content type
678        $header[] = 'Content-Type: text/plain; charset=UTF-8';
679        switch($this->getResponseFormat()) {
680          case self::_RESPONSE_JSON:
681            $header[] = 'x-li-format: json';
682            break;
683          case self::_RESPONSE_JSONP:
684            $header[] = 'x-li-format: jsonp';
685            break;
686        }
687      } else {
688        $header[] = 'Content-Type: text/xml; charset=UTF-8';
689        curl_setopt($handle, CURLOPT_POSTFIELDS, $data);
690      }
691      curl_setopt($handle, CURLOPT_HTTPHEADER, $header);
692   
693      // set the last url, headers
694      $this->last_request_url = $url;
695      $this->last_request_headers = $header;
696     
697      // gather the response
698      $return_data['linkedin']        = curl_exec($handle);
699      $return_data['info']            = curl_getinfo($handle);
700      $return_data['oauth']['header'] = $oauth_req->to_header(self::_API_OAUTH_REALM);
701      $return_data['oauth']['string'] = $oauth_req->base_string;
702           
703      // check for throttling
704      if(self::isThrottled($return_data['linkedin'])) {
705        throw new LinkedInException('LinkedIn->fetch(): throttling limit for this user/application has been reached for LinkedIn resource - ' . $url);
706      }
707     
708      //TODO - add check for NO response (http_code = 0) from cURL
709     
710      // close cURL connection
711      curl_close($handle);
712     
713      // no exceptions thrown, return the data
714      return $return_data;
715    } catch(OAuthException $e) {
716      // oauth exception raised
717      throw new LinkedInException('OAuth exception caught: ' . $e->getMessage());
718    }
719        }
720       
721        /**
722         * This flags a specified post as specified by type.
723         *
724         *   http://developer.linkedin.com/documents/groups-api
725         *
726         * @param str $pid
727         *              The post id.
728         * @param str $type
729         *              The type to flag the post as.
730         *
731         * @return arr
732         *              Array containing retrieval success, LinkedIn response.
733         */
734        public function flagPost($pid, $type) {
735                if(!is_string($pid)) {
736                        throw new LinkedInException('LinkedIn->flagPost(): bad data passed, $pid must be of type string');
737                }
738                if(!is_string($type)) {
739                        throw new LinkedInException('LinkedIn->flagPost(): bad data passed, $like must be of type string');
740                }
741                //Constructing the xml
742                $data = '<?xml version="1.0" encoding="UTF-8"?>';
743                switch($type) {
744                        case 'promotion':
745                                $data .= '<code>promotion</code>';
746                                break;
747                        case 'job':
748                                $data .= '<code>job</code>';
749                                break;
750                        default: 
751                                throw new LinkedInException('LinkedIn->flagPost(): invalid value for $type, must be one of: "promotion", "job"');
752                                break; 
753                }
754               
755                // construct and send the request
756                $query    = self::_URL_API . '/v1/posts/' . $pid . '/category/code';
757                $response = $this->fetch('PUT', $query, $data);
758                 
759        /**
760     * Check for successful request (a 204 response from LinkedIn server)
761           * per the documentation linked in method comments above.
762           */
763                return $this->checkResponse(204, $response);
764        }
765       
766        /**
767         * Follow a company.
768         *
769         * Calling this method causes the current user to start following the
770         * specified company, per:
771         *
772         *   http://developer.linkedin.com/docs/DOC-1324
773         *
774         * @param str $cid
775         *    Company ID you want to follow.
776         *               
777         * @return arr
778         *    Array containing retrieval success, LinkedIn response.
779         */
780        public function followCompany($cid) {
781          // check passed data
782          if(!is_string($cid)) {
783            // bad data passed
784                  throw new LinkedInException('LinkedIn->followCompany(): bad data passed, $cid must be of type string.');
785          }
786         
787          // construct and send the request
788          $query    = self::_URL_API . '/v1/people/~/following/companies';
789          $response = $this->fetch('POST', $query, '<company><id>' . trim($cid) . '</id></company>');
790         
791          /**
792           * Check for successful request (a 201 response from LinkedIn server)
793           * per the documentation linked in method comments above.
794           */
795          return $this->checkResponse(201, $response);
796        }
797       
798        /**
799         * Follows/Unfollows the specified post.
800         *
801         * https://developer.linkedin.com/documents/groups-api
802         *
803         * @param str $pid
804         *              The post id.
805         * @param bool $follow
806         *              Determines whether to follow or unfollow the post. TRUE = follow, FALSE = unfollow
807         *
808         * @return arr
809         *              Array containing retrieval success, LinkedIn response.
810         */
811       
812        public function followPost($pid, $follow) {
813                if(!is_string($pid)) {
814                        throw new LinkedInException('LinkedIn->followPost(): bad data passed, $pid must be of type string');
815                }
816                if(!($follow === TRUE || $follow === FALSE)) {
817                        throw new LinkedInException('LinkedIn->followPost(): bad data passed, $follow must be of type boolean');
818                }
819               
820                // construct the XML
821                $data = '<?xml version="1.0" encoding="UTF-8"?>
822                                     <is-following>'. (($follow) ? 'true' : 'false'). '</is-following>';
823               
824                // construct and send the request
825                $query    = self::_URL_API . '/v1/posts/' . trim($pid) . '/relation-to-viewer/is-following';
826                $response = $this->fetch('PUT', $query, $data);
827               
828                /**
829           * Check for successful request (a 204 response from LinkedIn server)
830           * per the documentation linked in method comments above.
831           */
832                return $this->checkResponse(204, $response);
833        }
834       
835        /**
836         * Get list of companies you follow.
837         *
838         * Returns a list of companies the current user is currently following, per:
839         *
840         *   http://developer.linkedin.com/docs/DOC-1324   
841         *     
842         * @return arr
843         *    Array containing retrieval success, LinkedIn response.
844         */
845        public function followedCompanies() {     
846          // construct and send the request
847    $query    = self::_URL_API . '/v1/people/~/following/companies';
848          $response = $this->fetch('GET', $query);
849         
850          /**
851           * Check for successful request (a 200 response from LinkedIn server)
852           * per the documentation linked in method comments above.
853           */
854          return $this->checkResponse(200, $response);
855        }
856       
857        /**
858         * Get the application_key property.
859         *
860         * @return str
861         *    The application key.               
862         */
863        public function getApplicationKey() {
864          return $this->application_key;
865        }
866       
867        /**
868         * Get the application_secret property.
869         *
870         * @return str
871         *    The application secret.           
872         */
873        public function getApplicationSecret() {
874          return $this->application_secret;
875        }
876       
877        /**
878         * Get the callback property.
879         *
880         * @return str
881         *    The callback url.         
882         */
883        public function getCallbackUrl() {
884          return $this->callback;
885        }
886 
887  /**
888         * Get the response_format property.
889         *
890         * @return str
891         *    The response format.               
892         */
893        public function getResponseFormat() {
894          return $this->response_format;
895        }
896       
897        /**
898         * Get the token_access property.
899         *
900         * @return arr
901         *    The access token.         
902         */
903        public function getToken() {
904          return $this->token;
905        }
906       
907        /**
908         * [DEPRECATED] Get the token_access property.
909         *
910         * @return arr
911         *    The access token.         
912         */
913        public function getTokenAccess() {
914          return $this->getToken();
915        }
916       
917        /**
918         *
919         * Get information about a specific group.
920         *
921         *   http://developer.linkedin.com/documents/groups-api
922         *
923         * @param str $gid
924         *              The group id.
925         * 
926         * @param str $options
927         *              [OPTIONAL] Field selectors for the group.
928         *
929         * @return arr
930         *              Array containing retrieval success, LinkedIn response.
931         */
932       
933        public function group($gid, $options = '') {
934                if(!is_string($gid)){
935                        throw new LinkedInException('LinkedIn->group(): bad data passed, $gid must be of type string.');
936                }
937                if(!is_string($options)) {
938                        throw new LinkedInException('LinkedIn->group(): bad data passed, $options must be of type string');
939                }
940       
941                // construct and send the request
942                $query    = self::_URL_API . '/v1/groups/' . trim($gid) . trim($options); 
943                $response = $this->fetch('GET', $query);
944               
945                /**
946           * Check for successful request (a 200 response from LinkedIn server)
947           * per the documentation linked in method comments above.
948           */
949                return $this->checkResponse(200, $response);
950        }
951       
952        /**
953         * This returns all the groups the user is a member of.
954         *
955         *   http://developer.linkedin.com/documents/groups-api
956         *
957         * @param str $options
958         *              [OPTIONAL] Field selectors for the groups.
959         *
960         * @return arr
961         *              Array containing retrieval success, LinkedIn response.
962         */
963        public function groupMemberships($options = '') {
964                if(!is_string($options)) {
965                        throw new LinkedInException('LinkedIn->groupMemberships(): bad data passed, $options must be of type string');
966                }
967               
968                // construct and send the request
969                $query    = self::_URL_API . '/v1/people/~/group-memberships' . trim($options) . '?membership-state=member';
970                $response = $this->fetch('GET', $query);
971               
972                /**
973           * Check for successful request (a 200 response from LinkedIn server)
974           * per the documentation linked in method comments above.
975           */
976                return $this->checkResponse(200, $response);
977        }
978       
979        /**
980         * This gets a specified post made within a group.
981         *
982         *   http://developer.linkedin.com/documents/groups-api
983         *
984         * @param str $pid
985         *              The post id.
986         * @param str $options
987         *              [OPTIONAL] Field selectors for the post.
988         *
989         * @return arr
990         *              Array containing retrieval success, LinkedIn response.
991         */
992        public function groupPost($pid, $options = '') {
993                if(!is_string($pid)) {
994                        throw new LinkedInException('LinkedIn->groupPost(): bad data passed, $pid must be of type string.');
995                }
996                if(!is_string($options)) {
997                        throw new LinkedInException('LinkedIn->groupPost(): bad data passed, $options must be of type string.');
998                }
999               
1000                // construct and send the request
1001                $query    = self::_URL_API . '/v1/posts/' . trim($pid) . trim($options);
1002                $response = $this->fetch('GET', $query);
1003               
1004                /**
1005           * Check for successful request (a 200 response from LinkedIn server)
1006           * per the documentation linked in method comments above.
1007           */
1008                return $this->checkResponse(200, $response);
1009        }
1010       
1011        /**
1012         * This returns all the comments made on the specified post within a group.
1013         *
1014         *   http://developer.linkedin.com/documents/groups-api
1015         *
1016         * @param str $pid
1017         *              The post id.
1018         * @param str $options
1019         *              [OPTIONAL] Field selectors for the post comments.
1020         *
1021         * @return arr
1022         *              Array containing retrieval success, LinkedIn response.
1023         */
1024        public function groupPostComments($pid, $options = ''){ 
1025                if(!is_string($pid)){
1026                        throw new LinkedInException('LinkedIn->groupPostComments(): bad data passed, $pid must be of type string.');
1027                }
1028                if(!is_string($options)) {
1029                        throw new LinkedInException('LinkedIn->groupPostComments(): bad data passed, $options must be of type string.');
1030                }               
1031               
1032                // construct and send the request
1033                $query    = self::_URL_API . '/v1/posts/' . trim($pid) . '/comments' . trim($options);
1034                $response = $this->fetch('GET', $query);
1035
1036                /**
1037           * Check for successful request (a 200 response from LinkedIn server)
1038           * per the documentation linked in method comments above.
1039           */
1040                return $this->checkResponse(200, $response);
1041        }
1042       
1043        /**
1044         * This returns all the posts within a group.
1045         *
1046         *   http://developer.linkedin.com/documents/groups-api
1047         *
1048         * @param str $gid
1049         *              The group id.
1050         *
1051         * @return arr
1052         *              Array containing retrieval success, LinkedIn response.
1053         */
1054        public function groupPosts($gid, $options = '') {
1055                if(!is_string($gid)){
1056                        throw new LinkedInException('LinkedIn->groupPosts(): bad data passed, $gid must be of type string');
1057                }
1058                if(!is_string($options)){
1059                        throw new LinkedInException('LinkedIn->groupPosts(): bad data passed, $options must be of type string');
1060                }
1061               
1062                // construct and send the request
1063                $query    = self::_URL_API . '/v1/groups/' . trim($gid)  .'/posts' . trim($options);
1064                $response = $this->fetch('GET', $query);
1065               
1066                /**
1067           * Check for successful request (a 200 response from LinkedIn server)
1068           * per the documentation linked in method comments above.
1069           */
1070                return $this->checkResponse(200, $response);
1071        }
1072       
1073        /**
1074         * This returns the group settings of the specified group
1075         *
1076         *   http://developer.linkedin.com/documents/groups-api
1077         *
1078         * @param str $gid
1079         *              The group id.
1080         * @param str $options
1081         *              [OPTIONAL] Field selectors for the group.
1082         *
1083         * @return arr
1084         *              Array containing retrieval success, LinkedIn response.
1085         */
1086        public function groupSettings($gid, $options = '') {
1087                if(!is_string($gid)) {
1088                        throw new LinkedInException('LinkedIn->groupSettings(): bad data passed, $gid must be of type string');
1089                }
1090                if(!is_string($options)) {
1091                        throw new LinkedInException('LinkedIn->groupSettings(): bad data passed, $options must be of type string');
1092                }
1093               
1094                // construct and send the request
1095                $query    = self::_URL_API . '/v1/people/~/group-memberships/' . trim($gid) . trim($options);
1096                $response = $this->fetch('GET', $query);
1097               
1098                /**
1099           * Check for successful request (a 200 response from LinkedIn server)
1100           * per the documentation linked in method comments above.
1101           */
1102                return $this->checkResponse(200, $response);
1103        }
1104       
1105        /**
1106         * Send connection invitations.
1107         *     
1108         * Send an invitation to connect to your network, either by email address or
1109         * by LinkedIn ID. Details on the API here:
1110         *
1111         *   http://developer.linkedin.com/docs/DOC-1012
1112         *
1113         * @param str $method
1114         *    The invitation method to process. 
1115         * @param str $recipient
1116         *    The email/id to send the invitation to.           
1117         * @param str $subject
1118         *    The subject of the invitation to send.
1119         * @param str $body
1120         *    The body of the invitation to send.
1121         * @param str $type
1122         *    [OPTIONAL] The invitation request type (only friend is supported at this time by the Invite API).
1123         *
1124         * @return arr
1125         *    Array containing retrieval success, LinkedIn response.     
1126         */
1127        public function invite($method, $recipient, $subject, $body, $type = 'friend') {
1128    /**
1129     * Clean up the passed data per these rules:
1130     *
1131     * 1) Message must be sent to one recipient (only a single recipient permitted for the Invitation API)
1132     * 2) No HTML permitted
1133     * 3) 200 characters max in the invitation subject
1134     * 4) Only able to connect as a friend at this point     
1135     */
1136    // check passed data
1137    if(empty($recipient)) {
1138                throw new LinkedInException('LinkedIn->invite(): you must provide an invitation recipient.');
1139    }
1140    switch($method) {
1141      case 'email':
1142        if(is_array($recipient)) {
1143          $recipient = array_map('trim', $recipient);
1144        } else {
1145          // bad format for recipient for email method
1146          throw new LinkedInException('LinkedIn->invite(): invitation recipient email/name array is malformed.');
1147        }
1148        break;
1149      case 'id':
1150        $recipient = trim($recipient);
1151        if(!self::isId($recipient)) {
1152          // bad format for recipient for id method
1153          throw new LinkedInException('LinkedIn->invite(): invitation recipient ID does not match LinkedIn format.');
1154        }
1155        break;
1156      default:
1157        throw new LinkedInException('LinkedIn->invite(): bad invitation method, must be one of: email, id.');
1158        break;
1159    }
1160    if(!empty($subject)) {
1161      $subject = trim(htmlspecialchars(strip_tags(stripslashes($subject))));
1162    } else {
1163      throw new LinkedInException('LinkedIn->invite(): message subject is empty.');
1164    }
1165    if(!empty($body)) {
1166      $body = trim(htmlspecialchars(strip_tags(stripslashes($body))));
1167      if(strlen($body) > self::_INV_BODY_LENGTH) {
1168        throw new LinkedInException('LinkedIn->invite(): message body length is too long - max length is ' . self::_INV_BODY_LENGTH . ' characters.');
1169      }
1170    } else {
1171      throw new LinkedInException('LinkedIn->invite(): message body is empty.');
1172    }
1173    switch($type) {
1174      case 'friend':
1175        break;
1176      default:
1177        throw new LinkedInException('LinkedIn->invite(): bad invitation type, must be one of: friend.');
1178        break;
1179    }
1180   
1181    // construct the xml data
1182                $data   = '<?xml version="1.0" encoding="UTF-8"?>
1183                           <mailbox-item>
1184                             <recipients>
1185                   <recipient>';
1186                     switch($method) {
1187                       case 'email':
1188                         // email-based invitation
1189                         $data .= '<person path="/people/email=' . $recipient['email'] . '">
1190                                     <first-name>' . htmlspecialchars($recipient['first-name']) . '</first-name>
1191                                     <last-name>' . htmlspecialchars($recipient['last-name']) . '</last-name>
1192                                   </person>';
1193                         break;
1194                       case 'id':
1195                         // id-based invitation
1196                         $data .= '<person path="/people/id=' . $recipient . '"/>';
1197                         break;
1198                     }
1199    $data  .= '    </recipient>
1200                 </recipients>
1201                 <subject>' . $subject . '</subject>
1202                 <body>' . $body . '</body>
1203                 <item-content>
1204                   <invitation-request>
1205                     <connect-type>';
1206                       switch($type) {
1207                         case 'friend':
1208                           $data .= 'friend';
1209                           break;
1210                       }
1211    $data  .= '      </connect-type>';
1212                     switch($method) {
1213                       case 'id':
1214                         // id-based invitation, we need to get the authorization information
1215                         $query                 = 'id=' . $recipient . ':(api-standard-profile-request)';
1216                         $response              = self::profile($query);
1217                         if($response['info']['http_code'] == 200) {
1218                           $response['linkedin'] = self::xmlToArray($response['linkedin']);
1219                           if($response['linkedin'] === FALSE) {
1220                             // bad XML data
1221                             throw new LinkedInException('LinkedIn->invite(): LinkedIn returned bad XML data.');
1222                           }
1223                           $authentication = explode(':', $response['linkedin']['person']['children']['api-standard-profile-request']['children']['headers']['children']['http-header']['children']['value']['content']);
1224                           
1225                           // complete the xml       
1226                           $data .= '<authorization>
1227                                       <name>' . $authentication[0] . '</name>
1228                                       <value>' . $authentication[1] . '</value>
1229                                     </authorization>';
1230                         } else {
1231                           // bad response from the profile request, not a valid ID?
1232                           throw new LinkedInException('LinkedIn->invite(): could not send invitation, LinkedIn says: ' . print_r($response['linkedin'], TRUE));
1233                         }
1234                         break;
1235                     }
1236    $data  .= '    </invitation-request>
1237                 </item-content>
1238               </mailbox-item>';
1239   
1240    // send request
1241    $query    = self::_URL_API . '/v1/people/~/mailbox';
1242    $response = $this->fetch('POST', $query, $data);
1243               
1244                /**
1245           * Check for successful request (a 201 response from LinkedIn server)
1246           * per the documentation linked in method comments above.
1247           */ 
1248    return $this->checkResponse(201, $response);
1249        }
1250       
1251        /**
1252         * LinkedIn ID validation.
1253         *       
1254         * Checks the passed string $id to see if it has a valid LinkedIn ID format,
1255         * which is, as of October 15th, 2010:
1256         *
1257         *   10 alpha-numeric mixed-case characters, plus underscores and dashes.               
1258         *
1259         * @param str $id
1260         *    A possible LinkedIn ID.           
1261         *
1262         * @return bool
1263         *    TRUE/FALSE depending on valid ID format determination.                 
1264         */
1265        public static function isId($id) {
1266          // check passed data
1267    if(!is_string($id)) {
1268            // bad data passed
1269            throw new LinkedInException('LinkedIn->isId(): bad data passed, $id must be of type string.');
1270          }
1271         
1272          $pattern = '/^[a-z0-9_\-]{10}$/i';
1273          if($match = preg_match($pattern, $id)) {
1274            // we have a match
1275            $return_data = TRUE;
1276          } else {
1277            // no match
1278            $return_data = FALSE;
1279          }
1280          return $return_data;
1281        }
1282       
1283        /**
1284         * Throttling check.
1285         *
1286         * Checks the passed LinkedIn response to see if we have hit a throttling
1287         * limit:
1288         *
1289         * http://developer.linkedin.com/docs/DOC-1112
1290         *
1291         * @param arr $response
1292         *    The LinkedIn response.
1293         *                       
1294         * @return bool
1295         *    TRUE/FALSE depending on content of response.                 
1296         */
1297        public static function isThrottled($response) {
1298          $return_data = FALSE;
1299   
1300    // check the variable
1301          if(!empty($response) && is_string($response)) {
1302            // we have an array and have a properly formatted LinkedIn response
1303               
1304      // store the response in a temp variable
1305      $temp_response = self::xmlToArray($response);
1306          if($temp_response !== FALSE) {
1307          // check to see if we have an error
1308          if(array_key_exists('error', $temp_response) && ($temp_response['error']['children']['status']['content'] == 403) && preg_match('/throttle/i', $temp_response['error']['children']['message']['content'])) {
1309            // we have an error, it is 403 and we have hit a throttle limit
1310              $return_data = TRUE;
1311          }
1312          }
1313        }
1314        return $return_data;
1315        }
1316       
1317        /**
1318         * Job posting detail info retrieval function.
1319         *
1320         * The Jobs API returns detailed information about job postings on LinkedIn.
1321         * Find the job summary, description, location, and apply our professional graph
1322         * to present the relationship between the current member and the job poster or
1323         * hiring manager.
1324         *
1325         *   http://developer.linkedin.com/docs/DOC-1322 
1326         *
1327         * @param       str $jid
1328         *    ID of the job you want to look up.
1329         * @param str $options
1330         *    [OPTIONAL] Data retrieval options.
1331         *             
1332         * @return arr
1333         *    Array containing retrieval success, LinkedIn response.
1334         */
1335        public function job($jid, $options = '') {
1336          // check passed data
1337          if(!is_string($jid)) {
1338            // bad data passed
1339                  throw new LinkedInException('LinkedIn->job(): bad data passed, $jid must be of type string.');
1340          }
1341          if(!is_string($options)) {
1342            // bad data passed
1343                  throw new LinkedInException('LinkedIn->job(): bad data passed, $options must be of type string.');
1344          }
1345         
1346          // construct and send the request
1347          $query    = self::_URL_API . '/v1/jobs/' . trim($jid) . trim($options);
1348          $response = $this->fetch('GET', $query);
1349         
1350          /**
1351           * Check for successful request (a 200 response from LinkedIn server)
1352           * per the documentation linked in method comments above.
1353           */
1354          return $this->checkResponse(200, $response);
1355        }
1356       
1357        /**
1358         * Join the specified group, per:
1359         *
1360         *   http://developer.linkedin.com/documents/groups-api
1361         *
1362         * @param str $gid
1363         *              The group id.
1364         *
1365         * @return arr
1366         *              Array containing retrieval success, LinkedIn response.           
1367         */
1368        public function joinGroup($gid) {
1369                if(!is_string($gid)) {
1370                        throw new LinkedInException('LinkedIn->joinGroup(): bad data passed, $gid must be of type string.');
1371                }
1372               
1373                // constructing the XML
1374                $data = '<?xml version="1.0" encoding="UTF-8"?>
1375                                   <group-membership>
1376                                         <membership-state>
1377                                                 <code>member</code>
1378                                         </membership-state>
1379                                   </group-membership>';
1380               
1381                // construct and send the request
1382                $query    = self::_URL_API . '/v1/people/~/group-memberships/' . trim($gid);
1383                $response = $this->fetch('PUT', $query, $data);
1384               
1385                /**
1386           * Check for successful request (a 200 or 201 response from LinkedIn server)
1387           * per the documentation linked in method comments above.
1388           */
1389                return $this->checkResponse(array(200, 201), $response);
1390        }
1391       
1392        /**
1393         * Returns the last request header from the previous call to the
1394         * LinkedIn API.
1395         *
1396         * @returns str
1397         *    The header, in string format.
1398         */             
1399        public function lastRequestHeader() {
1400           return $this->last_request_headers;
1401        }
1402       
1403        /**
1404         * Returns the last request url from the previous call to the
1405         * LinkedIn API.
1406         *
1407         * @returns str
1408         *    The url, in string format.
1409         */             
1410        public function lastRequestUrl() {
1411           return $this->last_request_url;
1412        }
1413       
1414        /**
1415         * Leave the specified group, per:.
1416         *
1417         *   http://developer.linkedin.com/documents/groups-api
1418         *
1419         * @param str $gid
1420         *              The group id.
1421         *
1422         * @return arr
1423         *              Array containing retrieval success, LinkedIn response.
1424         */
1425        public function leaveGroup($gid){
1426                if(!is_string($gid)) {
1427                        throw new LinkedInException('LinkedIn->leaveGroup(): bad data passed, $gid must be of type string');
1428                }
1429               
1430                // construct and send the request
1431                $query    = self::_URL_API . '/v1/people/~/group-memberships/'  .trim($gid);
1432                $response = $this->fetch('DELETE', $query);
1433               
1434                /**
1435           * Check for successful request (a 204 response from LinkedIn server)
1436           * per the documentation linked in method comments above.
1437           */ 
1438                return $this->checkResponse(204, $response);
1439        }
1440       
1441        /**
1442         * Like another user's network update, per:
1443         *
1444         *   http://developer.linkedin.com/docs/DOC-1043
1445         *
1446         * @param str $uid
1447         *    The LinkedIn update ID.
1448         *                       
1449         * @return arr
1450         *    Array containing retrieval success, LinkedIn response.                 
1451         */
1452        public function like($uid) {
1453          // check passed data
1454          if(!is_string($uid)) {
1455            // bad data passed
1456                  throw new LinkedInException('LinkedIn->like(): bad data passed, $uid must be of type string.');
1457          }
1458   
1459    // construct the XML
1460                $data = '<?xml version="1.0" encoding="UTF-8"?>
1461                         <is-liked>true</is-liked>';
1462               
1463                // construct and send the request
1464    $query    = self::_URL_API . '/v1/people/~/network/updates/key=' . $uid . '/is-liked';
1465    $response = $this->fetch('PUT', $query, $data);
1466   
1467        /**
1468           * Check for successful request (a 201 response from LinkedIn server)
1469           * per the documentation linked in method comments above.
1470           */ 
1471    return $this->checkResponse(201, $response);
1472        }
1473       
1474        /**
1475         * Likes/unlikes the specified post, per:
1476         *
1477         *   http://developer.linkedin.com/documents/groups-api
1478         *
1479         * @param str $pid
1480         *              The post id.
1481         * @param bool $like
1482         *              Determines whether to like or unlike. TRUE = like, FALSE = unlike.
1483         *
1484         * @return arr
1485         *              Array containing retrieval success, LinkedIn response.
1486         */
1487        public function likePost($pid, $like) {
1488                if(!is_string($pid)) {
1489                        throw new LinkedInException ('LinkedIn->likePost(): bad data passed, $pid must be of type string');
1490                }
1491                if(!($like === TRUE || $like === FALSE)) {
1492                        throw new LinkedInException('LinkedIn->likePost(): bad data passed, $like must be of type boolean');
1493                }
1494               
1495                // construct the XML
1496                $data = '<?xml version="1.0" encoding="UTF-8"?>
1497                         <is-liked>'.(($like) ? 'true': 'false').'</is-liked>';
1498               
1499                // construct and send the request
1500                $query    = self::_URL_API . '/v1/posts/' . trim($pid) . '/relation-to-viewer/is-liked';
1501                $response = $this->fetch('PUT', $query, $data);
1502               
1503                /**
1504           * Check for successful request (a 204 response from LinkedIn server)
1505           * per the documentation linked in method comments above.
1506           */ 
1507                return $this->checkResponse(204, $response);
1508        }
1509       
1510        /**
1511         * Retrieve network update likes.
1512         *   
1513         * Return all likes associated with a given network update:
1514         *
1515         * http://developer.linkedin.com/docs/DOC-1043
1516         *
1517         * @param str $uid
1518         *    The LinkedIn update ID.
1519         *                       
1520         * @return arr
1521         *    Array containing retrieval success, LinkedIn response.                 
1522         */
1523        public function likes($uid) {
1524          // check passed data
1525          if(!is_string($uid)) {
1526            // bad data passed
1527                  throw new LinkedInException('LinkedIn->likes(): bad data passed, $uid must be of type string.');
1528          }
1529               
1530                // construct and send the request
1531    $query    = self::_URL_API . '/v1/people/~/network/updates/key=' . $uid . '/likes';
1532    $response = $this->fetch('GET', $query);
1533   
1534        /**
1535           * Check for successful request (a 200 response from LinkedIn server)
1536           * per the documentation linked in method comments above.
1537           */ 
1538    return $this->checkResponse(200, $response);
1539        }
1540       
1541        /**
1542         * Connection messaging method.
1543         *       
1544         * Send a message to your network connection(s), optionally copying yourself. 
1545         * Full details from LinkedIn on this functionality can be found here:
1546         *
1547         *   http://developer.linkedin.com/docs/DOC-1044
1548         *
1549         * @param arr $recipients
1550         *    The connection(s) to send the message to.         
1551         * @param str $subject
1552         *    The subject of the message to send.
1553         * @param str $body
1554         *    The body of the message to send.
1555         * @param bool $copy_self
1556         *    [OPTIONAL] Also update the teathered Twitter account.
1557         *       
1558         * @return arr
1559         *    Array containing retrieval success, LinkedIn response.             
1560         */
1561        public function message($recipients, $subject, $body, $copy_self = FALSE) {
1562    /**
1563     * Clean up the passed data per these rules:
1564     *
1565     * 1) Message must be sent to at least one recipient
1566     * 2) No HTML permitted
1567     */
1568    if(!empty($subject) && is_string($subject)) {
1569      $subject = trim(strip_tags(stripslashes($subject)));
1570    } else {
1571      throw new LinkedInException('LinkedIn->message(): bad data passed, $subject must be of type string.');
1572    }
1573    if(!empty($body) && is_string($body)) {
1574      $body = trim(strip_tags(stripslashes($body)));
1575    } else {
1576      throw new LinkedInException('LinkedIn->message(): bad data passed, $body must be of type string.');
1577    }
1578    if(!is_array($recipients) || count($recipients) < 1) {
1579      // no recipients, and/or bad data
1580      throw new LinkedInException('LinkedIn->message(): at least one message recipient required.');
1581    }
1582   
1583    // construct the xml data
1584                $data   = '<?xml version="1.0" encoding="UTF-8"?>
1585                           <mailbox-item>
1586                             <recipients>';
1587    $data  .=     ($copy_self) ? '<recipient><person path="/people/~"/></recipient>' : '';
1588                  for($i = 0; $i < count($recipients); $i++) {
1589                    if(is_string($recipients[$i])) {
1590                      $data .= '<recipient><person path="/people/' . trim($recipients[$i]) . '"/></recipient>';
1591                    } else {
1592                      throw new LinkedInException ('LinkedIn->message(): bad data passed, $recipients must be an array of type string.');
1593                    }
1594                  }
1595    $data  .= '  </recipients>
1596                 <subject>' . htmlspecialchars($subject) . '</subject>
1597                 <body>' . htmlspecialchars($body) . '</body>
1598               </mailbox-item>';
1599   
1600    // send request
1601    $query    = self::_URL_API . '/v1/people/~/mailbox';
1602    $response = $this->fetch('POST', $query, $data);
1603               
1604                /**
1605           * Check for successful request (a 201 response from LinkedIn server)
1606           * per the documentation linked in method comments above.
1607           */ 
1608    return $this->checkResponse(201, $response);
1609        }
1610       
1611        /**
1612         * Job posting method.
1613         *       
1614         * Post a job to LinkedIn, assuming that you have access to this feature.
1615         * Full details from LinkedIn on this functionality can be found here:
1616         *
1617         *   http://developer.linkedin.com/community/jobs?view=documents
1618         *
1619         * @param str $xml
1620         *    The XML defining a job to post.           
1621         *       
1622         * @return arr
1623         *    Array containing retrieval success, LinkedIn response.             
1624         */
1625        public function postJob($xml) {
1626    // check passed data
1627    if(is_string($xml)) {
1628      $xml = trim(stripslashes($xml));
1629    } else {
1630      throw new LinkedInException('LinkedIn->postJob(): bad data passed, $xml must be of type string.');
1631    }
1632   
1633    // construct and send the request
1634    $query    = self::_URL_API . '/v1/jobs';
1635    $response = $this->fetch('POST', $query, $xml);
1636               
1637                /**
1638           * Check for successful request (a 201 response from LinkedIn server)
1639           * per the documentation linked in method comments above.
1640           */ 
1641    return $this->checkResponse(201, $response);
1642        }
1643       
1644        /**
1645         * General profile retrieval function.
1646         *
1647         * Takes a string of parameters as input and requests profile data from the
1648         * Linkedin Profile API. See the official documentation for $options
1649         * 'field selector' formatting:
1650         *
1651         *   http://developer.linkedin.com/docs/DOC-1014
1652         *   http://developer.linkedin.com/docs/DOC-1002   
1653         *
1654         * @param str $options
1655         *    [OPTIONAL] Data retrieval options.
1656         *               
1657         * @return arr
1658         *    Array containing retrieval success, LinkedIn response.
1659         */
1660        public function profile($options = '~') {
1661          // check passed data
1662          if(!is_string($options)) {
1663            // bad data passed
1664                  throw new LinkedInException('LinkedIn->profile(): bad data passed, $options must be of type string.');
1665          }
1666         
1667          // construct and send the request
1668          $query    = self::_URL_API . '/v1/people/' . trim($options);
1669          $response = $this->fetch('GET', $query);
1670         
1671          /**
1672           * Check for successful request (a 200 response from LinkedIn server)
1673           * per the documentation linked in method comments above.
1674           */
1675          return $this->checkResponse(200, $response);
1676        }
1677       
1678        /**
1679         * Manual API call method, allowing for support for un-implemented API
1680         * functionality to be supported.
1681         *
1682         * @param str $method
1683         *    The data communication method.     
1684         * @param str $url
1685         *    The Linkedin API endpoint to connect with - should NOT include the
1686         *    leading https://api.linkedin.com/v1.
1687         * @param str $body
1688         *    [OPTIONAL] The URL-encoded body data to send to LinkedIn with the request.
1689         *
1690         * @return arr
1691         *              Array containing retrieval information, LinkedIn response. Note that you
1692         *              must manually check the return code and compare this to the expected
1693         *              API response to determine  if the raw call was successful.
1694         */
1695        public function raw($method, $url, $body = NULL) {
1696          if(!is_string($method)) {
1697            // bad data passed
1698                  throw new LinkedInException('LinkedIn->raw(): bad data passed, $method must be of string value.');
1699          }
1700          if(!is_string($url)) {
1701            // bad data passed
1702                  throw new LinkedInException('LinkedIn->raw(): bad data passed, $url must be of string value.');
1703          }
1704          if(!is_null($body) && !is_string($url)) {
1705            // bad data passed
1706                  throw new LinkedInException('LinkedIn->raw(): bad data passed, $body must be of string value.');
1707          }
1708   
1709    // construct and send the request
1710          $query = self::_URL_API . '/v1' . trim($url);
1711          return $this->fetch($method, $query, $body);
1712        }
1713       
1714        /**
1715         * This removes the specified group from the group suggestions, per:
1716         *
1717         *   http://developer.linkedin.com/documents/groups-api
1718         *
1719         * @param str $gid
1720         *              The group id.
1721         *
1722         * @return arr
1723         *              Array containing retrieval success, LinkedIn response.
1724         */
1725        public function removeSuggestedGroup($gid) {
1726                if(!is_string($gid)) {
1727                        throw new LinkedInException('LinkedIn->removeSuggestedGroup(): bad data passed, $gid must be of type string');
1728                } 
1729               
1730                // construct and send the request
1731                $query    = self::_URL_API . '/v1/people/~/suggestions/groups/'  .trim($gid);
1732                $response = $this->fetch('DELETE', $query);
1733               
1734                /**
1735           * Check for successful request (a 204 response from LinkedIn server)
1736           * per the documentation linked in method comments above.
1737           */
1738                return $this->checkResponse(204, $response);
1739        }
1740       
1741        /**
1742         * Renew a job.
1743         *
1744         * Calling this method causes the passed job to be renewed, per:
1745         *
1746         *   http://developer.linkedin.com/docs/DOC-1154   
1747         *
1748         * @param str $jid
1749         *    Job ID you want to renew.
1750         * @param str $cid
1751         *    Contract ID that covers the passed Job ID.         
1752         *             
1753         * @return arr
1754         *    Array containing retrieval success, LinkedIn response.
1755         */
1756        public function renewJob($jid, $cid) {
1757          // check passed data
1758          if(!is_string($jid)) {
1759            // bad data passed
1760                  throw new LinkedInException('LinkedIn->renewJob(): bad data passed, $jid must be of string value.');
1761          }
1762          if(!is_string($cid)) {
1763            // bad data passed
1764                  throw new LinkedInException('LinkedIn->renewJob(): bad data passed, $cid must be of string value.');
1765          }
1766         
1767          // construct the xml data
1768                $data   = '<?xml version="1.0" encoding="UTF-8"?>
1769                           <job>
1770                             <contract-id>' . trim($cid) . '</contract-id>
1771                 <renewal/>
1772               </job>';
1773               
1774          // construct and send the request
1775          $query    = self::_URL_API . '/v1/jobs/partner-job-id=' . trim($jid);
1776          $response = $this->fetch('PUT', $query, $data);
1777         
1778          /**
1779           * Check for successful request (a 200 response from LinkedIn server)
1780           * per the documentation linked in method comments above.
1781           */
1782          return $this->checkResponse(200, $response);
1783        }
1784       
1785  /**
1786         * Access token retrieval.
1787         *
1788         * Request the user's access token from the Linkedin API.
1789         *
1790         * @param str $token
1791         *    The token returned from the user authorization stage.
1792         * @param str $secret
1793         *    The secret returned from the request token stage.
1794         * @param str $verifier
1795         *    The verification value from LinkedIn.
1796         *       
1797         * @return arr
1798         *    The Linkedin OAuth/http response, in array format.         
1799         */
1800        public function retrieveTokenAccess($token, $secret, $verifier) {
1801          // check passed data
1802    if(!is_string($token) || !is_string($secret) || !is_string($verifier)) {
1803      // nothing passed, raise an exception
1804                  throw new LinkedInException('LinkedIn->retrieveTokenAccess(): bad data passed, string type is required for $token, $secret and $verifier.');
1805    }
1806   
1807    // start retrieval process
1808          $this->setToken(array('oauth_token' => $token, 'oauth_token_secret' => $secret));
1809    $parameters = array(
1810      'oauth_verifier' => $verifier
1811    );
1812    $response = $this->fetch(self::_METHOD_TOKENS, self::_URL_ACCESS, NULL, $parameters);
1813    parse_str($response['linkedin'], $response['linkedin']);
1814   
1815    /**
1816           * Check for successful request (a 200 response from LinkedIn server)
1817           * per the documentation linked in method comments above.
1818           */
1819    if($response['info']['http_code'] == 200) {
1820      // tokens retrieved
1821      $this->setToken($response['linkedin']);
1822     
1823      // set the response
1824      $return_data            = $response;
1825      $return_data['success'] = TRUE;
1826    } else {
1827      // error getting the request tokens
1828       $this->setToken(NULL);
1829       
1830      // set the response
1831      $return_data            = $response;
1832      $return_data['error']   = 'HTTP response from LinkedIn end-point was not code 200';
1833      $return_data['success'] = FALSE;
1834    }
1835    return $return_data;
1836        }
1837       
1838        /**
1839         * Request token retrieval.
1840         *
1841         * Get the request token from the Linkedin API.
1842         *
1843         * @return arr
1844         *    The Linkedin OAuth/http response, in array format.         
1845         */
1846        public function retrieveTokenRequest() {
1847    $parameters = array(
1848      'oauth_callback' => $this->getCallbackUrl()
1849    );
1850    $response = $this->fetch(self::_METHOD_TOKENS, self::_URL_REQUEST, NULL, $parameters);
1851    parse_str($response['linkedin'], $response['linkedin']);
1852   
1853    /**
1854           * Check for successful request (a 200 response from LinkedIn server)
1855           * per the documentation linked in method comments above.
1856           */
1857    if(($response['info']['http_code'] == 200) && (array_key_exists('oauth_callback_confirmed', $response['linkedin'])) && ($response['linkedin']['oauth_callback_confirmed'] == 'true')) {
1858      // tokens retrieved
1859      $this->setToken($response['linkedin']);
1860     
1861      // set the response
1862      $return_data            = $response;
1863      $return_data['success'] = TRUE;       
1864    } else {
1865      // error getting the request tokens
1866      $this->setToken(NULL);
1867     
1868      // set the response
1869      $return_data = $response;
1870      if((array_key_exists('oauth_callback_confirmed', $response['linkedin'])) && ($response['linkedin']['oauth_callback_confirmed'] == 'true')) {
1871        $return_data['error'] = 'HTTP response from LinkedIn end-point was not code 200';
1872      } else {
1873        $return_data['error'] = 'OAuth callback URL was not confirmed by the LinkedIn end-point';
1874      }
1875      $return_data['success'] = FALSE;
1876    }
1877    return $return_data;
1878        }
1879       
1880        /**
1881         * User authorization revocation.
1882         *
1883         * Revoke the current user's access token, clear the access token's from
1884         * current LinkedIn object. The current documentation for this feature is
1885         * found in a blog entry from April 29th, 2010:
1886         *
1887         *   http://developer.linkedin.com/community/apis/blog/2010/04/29/oauth--now-for-authentication 
1888         *
1889         * @return arr
1890         *    Array containing retrieval success, LinkedIn response.     
1891         */
1892        public function revoke() {
1893          // construct and send the request
1894          $response = $this->fetch('GET', self::_URL_REVOKE);
1895
1896          /**
1897           * Check for successful request (a 200 response from LinkedIn server)
1898           * per the documentation linked in method comments above.
1899           */                     
1900    return $this->checkResponse(200, $response);
1901        }
1902       
1903        /**
1904         * [DEPRECATED] General people search function.
1905         *
1906         * Takes a string of parameters as input and requests profile data from the
1907         * Linkedin People Search API.  See the official documentation for $options
1908         * querystring formatting:
1909         *
1910         *   http://developer.linkedin.com/docs/DOC-1191
1911         *
1912         * @param str $options
1913         *    [OPTIONAL] Data retrieval options.
1914         *               
1915         * @return arr
1916         *    Array containing retrieval success, LinkedIn response.
1917         */
1918        public function search($options = NULL) {
1919                return searchPeople($options);
1920        }
1921       
1922        /**
1923         * Company search.
1924         *
1925         * Uses the Company Search API to find companies using keywords, industry,
1926         * location, or some other criteria. It returns a collection of matching
1927         * companies.
1928         *
1929         *   http://developer.linkedin.com/docs/DOC-1325 
1930         *
1931         * @param str $options
1932         *    [OPTIONAL] Search options.       
1933         * @return arr
1934         *    Array containing retrieval success, LinkedIn response.
1935         */
1936        public function searchCompanies($options = '') {
1937          // check passed data
1938          if(!is_string($options)) {
1939            // bad data passed
1940                  throw new LinkedInException('LinkedIn->searchCompanies(): bad data passed, $options must be of type string.');
1941          }
1942         
1943          // construct and send the request
1944          $query    = self::_URL_API . '/v1/company-search' . trim($options);
1945          $response = $this->fetch('GET', $query);
1946         
1947          /**
1948           * Check for successful request (a 200 response from LinkedIn server)
1949           * per the documentation linked in method comments above.
1950           */
1951          return $this->checkResponse(200, $response);
1952        }
1953       
1954        /**
1955         * Jobs search.
1956         *
1957         * Use the Job Search API to find jobs using keywords, company, location,
1958         * or some other criteria. It returns a collection of matching jobs. Each
1959         * entry can contain much of the information available on the job listing.
1960         *
1961         *   http://developer.linkedin.com/docs/DOC-1321 
1962         *
1963         * @param str $options
1964         *    [OPTIONAL] Data retrieval options.
1965         *             
1966         * @return arr
1967         *    Array containing retrieval success, LinkedIn response.
1968         */
1969        public function searchJobs($options = '') {
1970          // check passed data
1971          if(!is_string($options)) {
1972            // bad data passed
1973                  throw new LinkedInException('LinkedIn->jobsSearch(): bad data passed, $options must be of type string.');
1974          }
1975         
1976          // construct and send the request
1977          $query    = self::_URL_API . '/v1/job-search' . trim($options);
1978          $response = $this->fetch('GET', $query);
1979         
1980          /**
1981           * Check for successful request (a 200 response from LinkedIn server)
1982           * per the documentation linked in method comments above.
1983           */
1984          return $this->checkResponse(200, $response);
1985        }
1986       
1987        /**
1988         * General people search function.
1989         *
1990         * Takes a string of parameters as input and requests profile data from the
1991         * Linkedin People Search API.  See the official documentation for $options
1992         * querystring formatting:
1993         *
1994         *   http://developer.linkedin.com/docs/DOC-1191
1995         *
1996         * @param str $options
1997         *    [OPTIONAL] Data retrieval options.
1998         *               
1999         * @return arr
2000         *    Array containing retrieval success, LinkedIn response.
2001         */
2002        public function searchPeople($options = NULL) {
2003          // check passed data
2004    if(!is_null($options) && !is_string($options)) {
2005            // bad data passed
2006                  throw new LinkedInException('LinkedIn->search(): bad data passed, $options must be of type string.');
2007          }
2008         
2009          // construct and send the request
2010    $query    = self::_URL_API . '/v1/people-search' . trim($options);
2011                $response = $this->fetch('GET', $query);
2012               
2013                /**
2014           * Check for successful request (a 200 response from LinkedIn server)
2015           * per the documentation linked in method comments above.
2016           */
2017                return $this->checkResponse(200, $response);
2018        }
2019       
2020        /**
2021         * Set the application_key property.
2022         *
2023         * @param str $key
2024         *    The application key.               
2025         */
2026        public function setApplicationKey($key) {
2027          $this->application_key = $key;
2028        }
2029       
2030        /**
2031         * Set the application_secret property.
2032         *
2033         * @param str $secret
2034         *    The application secret.           
2035         */
2036        public function setApplicationSecret($secret) {
2037          $this->application_secret = $secret;
2038        }
2039       
2040        /**
2041         * Set the callback property.
2042         *
2043         * @param str $url
2044         *    The callback url.         
2045         */
2046        public function setCallbackUrl($url) {
2047          $this->callback = $url;
2048        }
2049       
2050        /**
2051         * This sets the group settings of the specified group.
2052         *
2053         *   http://developer.linkedin.com/documents/groups-api
2054         *
2055         * @param str $gid
2056         *              The group id.
2057         * @param str $xml
2058         *              The group settings to set. The settings are:
2059         *                -<show-group-logo-in-profle>
2060         *                -<contact-email>
2061         *                -<email-digest-frequency>
2062         *                -<email-annoucements-from-managers>
2063         *                -<allow-messages-from-members>
2064         *                -<email-for-every-new-post>
2065         *
2066         * @return arr
2067         *              Array containing retrieval success, LinkedIn response.
2068         */
2069        public function setGroupSettings($gid, $xml) {
2070                if(!is_string ($gid)) {
2071      throw new LinkedInException('LinkedIn->setGroupSettings(): bad data passed, $token_access should be in array format.');
2072                }
2073                if(!is_string ($xml)) {
2074      throw new LinkedInException('LinkedIn->setGroupSettings(): bad data passed, $token_access should be in array format.');
2075                }
2076               
2077                // construct and send the request
2078                $query    = self::_URL_API . '/v1/people/~/group-memberships/' . trim($gid);
2079                $response = $this->fetch('PUT', $query, $xml);
2080               
2081          /**
2082           * Check for successful request (a 200 response from LinkedIn server)
2083           * per the documentation linked in method comments above.
2084           */ 
2085                return $this->checkResponse(200, $response);
2086        }
2087       
2088        /**
2089         * Set the response_format property.
2090         *
2091         * @param str $format
2092         *    [OPTIONAL] The response format to specify to LinkedIn.             
2093         */
2094        public function setResponseFormat($format = self::_DEFAULT_RESPONSE_FORMAT) {
2095          $this->response_format = $format;
2096        }
2097       
2098        /**
2099         * Set the token property.
2100         *
2101         * @return arr $token
2102         *    The LinkedIn OAuth token.
2103         */
2104        public function setToken($token) {
2105    // check passed data
2106    if(!is_null($token) && !is_array($token)) {
2107      // bad data passed
2108      throw new LinkedInException('LinkedIn->setToken(): bad data passed, $token_access should be in array format.');
2109    }
2110   
2111    // set token
2112    $this->token = $token;
2113        }
2114       
2115        /**
2116         * [DEPRECATED] Set the token_access property.
2117         *
2118         * @return arr $token_access
2119         *    [OPTIONAL] The LinkedIn OAuth access token.
2120         */
2121        public function setTokenAccess($token_access) {
2122    $this->setToken($token_access);
2123        }
2124       
2125        /**
2126         * Post a share.
2127         *
2128         * Create a new or reshare another user's shared content. Full details from
2129         * LinkedIn on this functionality can be found here:
2130         *
2131         *   http://developer.linkedin.com/docs/DOC-1212
2132         *
2133         *   $action values: ('new', 'reshare')         
2134         *   $content format:
2135         *     $action = 'new'; $content => ('comment' => 'xxx', 'title' => 'xxx', 'submitted-url' => 'xxx', 'submitted-image-url' => 'xxx', 'description' => 'xxx')
2136         *     $action = 'reshare'; $content => ('comment' => 'xxx', 'id' => 'xxx')     
2137         *
2138         * @param str $action
2139         *    The sharing action to perform.     
2140         * @param str $content
2141         *    The share content.
2142         * @param bool $private
2143         *    [OPTIONAL] Should we restrict this shared item to connections only?       
2144         * @param bool $twitter
2145         *    [OPTIONAL] Also update the teathered Twitter account.
2146         *       
2147         * @return arr
2148         *    Array containing retrieval success, LinkedIn response.             
2149         */
2150        public function share($action, $content, $private = TRUE, $twitter = FALSE) {
2151          // check the status itself
2152    if(!empty($action) && !empty($content)) {
2153      /**
2154       * Status is not empty, wrap a cleaned version of it in xml.  Status
2155       * rules:
2156       *
2157       * 1) Comments are 700 chars max (if this changes, change _SHARE_COMMENT_LENGTH constant)
2158       * 2) Content/title 200 chars max (if this changes, change _SHARE_CONTENT_TITLE_LENGTH constant)
2159       * 3) Content/description 400 chars max (if this changes, change _SHARE_CONTENT_DESC_LENGTH constant)
2160       * 4a) New shares must contain a comment and/or (content/title and content/submitted-url)
2161       * 4b) Reshared content must contain an attribution id.
2162       * 4c) Reshared content must contain actual content, not just a comment.             
2163       * 5) No HTML permitted in comment, content/title, content/description.
2164       */
2165
2166      // prepare the share data per the rules above
2167      $share_flag   = FALSE;
2168      $content_xml  = NULL;
2169      switch($action) {
2170        case 'new':
2171          // share can be an article
2172          if(array_key_exists('title', $content) && array_key_exists('submitted-url', $content)) {
2173            // we have shared content, format it as needed per rules above
2174            $content_title = trim(htmlspecialchars(strip_tags(stripslashes($content['title']))));
2175            if(strlen($content_title) > self::_SHARE_CONTENT_TITLE_LENGTH) {
2176              throw new LinkedInException('LinkedIn->share(): title length is too long - max length is ' . self::_SHARE_CONTENT_TITLE_LENGTH . ' characters.');
2177            }
2178            $content_xml .= '<content>
2179                               <title>' . $content_title . '</title>
2180                               <submitted-url>' . trim(htmlspecialchars($content['submitted-url'])) . '</submitted-url>';
2181            if(array_key_exists('submitted-image-url', $content)) {
2182              $content_xml .= '<submitted-image-url>' . trim(htmlspecialchars($content['submitted-image-url'])) . '</submitted-image-url>';
2183            }
2184            if(array_key_exists('description', $content)) {
2185              $content_desc = trim(htmlspecialchars(strip_tags(stripslashes($content['description']))));
2186              if(strlen($content_desc) > self::_SHARE_CONTENT_DESC_LENGTH) {
2187                throw new LinkedInException('LinkedIn->share(): description length is too long - max length is ' . self::_SHARE_CONTENT_DESC_LENGTH . ' characters.');
2188              }
2189              $content_xml .= '<description>' . $content_desc . '</description>';
2190            }
2191            $content_xml .= '</content>';
2192           
2193            $share_flag = TRUE;
2194          }
2195         
2196          // share can be just a comment
2197          if(array_key_exists('comment', $content)) {
2198                // comment located
2199                $comment = htmlspecialchars(trim(strip_tags(stripslashes($content['comment']))));
2200                if(strlen($comment) > self::_SHARE_COMMENT_LENGTH) {
2201              throw new LinkedInException('LinkedIn->share(): comment length is too long - max length is ' . self::_SHARE_COMMENT_LENGTH . ' characters.');
2202            }
2203            $content_xml .= '<comment>' . $comment . '</comment>';
2204               
2205                $share_flag = TRUE; 
2206          }
2207          break;
2208        case 'reshare':
2209          if(array_key_exists('id', $content)) {
2210            // put together the re-share attribution XML
2211            $content_xml .= '<attribution>
2212                               <share>
2213                                 <id>' . trim($content['id']) . '</id>
2214                               </share>
2215                             </attribution>';
2216           
2217            // optional additional comment
2218            if(array_key_exists('comment', $content)) {
2219                // comment located
2220                $comment = htmlspecialchars(trim(strip_tags(stripslashes($content['comment']))));
2221                if(strlen($comment) > self::_SHARE_COMMENT_LENGTH) {
2222                throw new LinkedInException('LinkedIn->share(): comment length is too long - max length is ' . self::_SHARE_COMMENT_LENGTH . ' characters.');
2223              }
2224              $content_xml .= '<comment>' . $comment . '</comment>';
2225                  }
2226                 
2227                  $share_flag = TRUE;
2228          }
2229          break;
2230        default:
2231          // bad action passed
2232          throw new LinkedInException('LinkedIn->share(): share action is an invalid value, must be one of: share, reshare.');
2233          break;
2234      }
2235     
2236      // should we proceed?
2237      if($share_flag) {
2238        // put all of the xml together
2239        $visibility = ($private) ? 'connections-only' : 'anyone';
2240        $data       = '<?xml version="1.0" encoding="UTF-8"?>
2241                       <share>
2242                         ' . $content_xml . '
2243                         <visibility>
2244                           <code>' . $visibility . '</code>
2245                         </visibility>
2246                       </share>';
2247       
2248        // create the proper url
2249        $share_url = self::_URL_API . '/v1/people/~/shares';
2250                  if($twitter) {
2251                          // update twitter as well
2252          $share_url .= '?twitter-post=true';
2253                        }
2254       
2255        // send request
2256        $response = $this->fetch('POST', $share_url, $data);
2257                } else {
2258                  // data contraints/rules not met, raise an exception
2259                    throw new LinkedInException('LinkedIn->share(): sharing data constraints not met; check that you have supplied valid content and combinations of content to share.');
2260                }
2261    } else {
2262      // data missing, raise an exception
2263                  throw new LinkedInException('LinkedIn->share(): sharing action or shared content is missing.');
2264    }
2265   
2266    /**
2267           * Check for successful request (a 201 response from LinkedIn server)
2268           * per the documentation linked in method comments above.
2269           */ 
2270    return $this->checkResponse(201, $response);
2271        }
2272       
2273        /**
2274         * Network statistics.
2275         *
2276         * General network statistics retrieval function, returns the number of connections,
2277         * second-connections an authenticated user has. More information here:
2278         *
2279         *   http://developer.linkedin.com/docs/DOC-1006
2280         *
2281         * @return arr
2282         *    Array containing retrieval success, LinkedIn response.
2283         */
2284        public function statistics() {
2285          // construct and send the request
2286    $query    = self::_URL_API . '/v1/people/~/network/network-stats';
2287                $response = $this->fetch('GET', $query);
2288               
2289                /**
2290           * Check for successful request (a 200 response from LinkedIn server)
2291           * per the documentation linked in method comments above.
2292           */
2293                return $this->checkResponse(200, $response);
2294        }
2295       
2296        /**
2297         * Companies you may want to follow.
2298         *
2299         * Returns a list of companies the current user may want to follow, per:
2300         *
2301         *   http://developer.linkedin.com/docs/DOC-1324   
2302         *
2303         * @return arr
2304         *    Array containing retrieval success, LinkedIn response.
2305         */
2306        public function suggestedCompanies() {
2307          // construct and send the request
2308    $query    = self::_URL_API . '/v1/people/~/suggestions/to-follow/companies';
2309          $response = $this->fetch('GET', $query);
2310         
2311          /**
2312           * Check for successful request (a 200 response from LinkedIn server)
2313           * per the documentation linked in method comments above.
2314           */
2315          return $this->checkResponse(200, $response);
2316        }
2317       
2318        /**
2319         * Retrieves suggested groups for the user, per:
2320         *
2321         *   http://developer.linkedin.com/documents/groups-api
2322         *
2323         * @return arr
2324         *              Array containing retrieval success, LinkedIn response.
2325         */
2326        public function suggestedGroups() {
2327                // construct and send the request
2328                $query    = self::_URL_API . '/v1/people/~/suggestions/groups:(id,name,is-open-to-non-members)';
2329                $response = $this->fetch('GET', $query);
2330               
2331                /**
2332           * Check for successful request (a 200 response from LinkedIn server)
2333           * per the documentation linked in method comments above.
2334           */
2335                return $this->checkResponse (200, $response);
2336        }
2337
2338        /**
2339         * Jobs you may be interested in.
2340         *
2341         * Returns a list of jobs the current user may be interested in, per:
2342         *
2343         *   http://developer.linkedin.com/docs/DOC-1323   
2344         *
2345         * @param str $options
2346         *    [OPTIONAL] Data retrieval options.       
2347         *               
2348         * @return arr
2349         *    Array containing retrieval success, LinkedIn response.
2350         */
2351        public function suggestedJobs($options = ':(jobs)') {
2352          // check passed data
2353          if(!is_string($options)) {
2354            // bad data passed
2355                  throw new LinkedInException('LinkedIn->suggestedJobs(): bad data passed, $options must be of type string.');
2356          }
2357       
2358          // construct and send the request
2359          $query    = self::_URL_API . '/v1/people/~/suggestions/job-suggestions' . trim($options);
2360          $response = $this->fetch('GET', $query);
2361         
2362          /**
2363           * Check for successful request (a 200 response from LinkedIn server)
2364           * per the documentation linked in method comments above.
2365           */
2366          return $this->checkResponse(200, $response);
2367        }
2368       
2369        /**
2370         * Unbookmark a job.
2371         *
2372         * Calling this method causes the current user to remove a bookmark for the
2373         * specified job:
2374         *
2375         *   http://developer.linkedin.com/docs/DOC-1323   
2376         *
2377         * @param str $jid
2378         *    Job ID you want to unbookmark.
2379         *             
2380         * @return arr
2381         *    Array containing retrieval success, LinkedIn response.
2382         */
2383        public function unbookmarkJob($jid) {
2384          // check passed data
2385          if(!is_string($jid)) {
2386            // bad data passed
2387                  throw new LinkedInException('LinkedIn->unbookmarkJob(): bad data passed, $jid must be of type string.');
2388          }
2389         
2390          // construct and send the request
2391          $query    = self::_URL_API . '/v1/people/~/job-bookmarks/' . trim($jid);
2392          $response = $this->fetch('DELETE', $query);
2393         
2394          /**
2395           * Check for successful request (a 204 response from LinkedIn server)
2396           * per the documentation linked in method comments above.
2397           */
2398          return $this->checkResponse(204, $response);
2399        }
2400       
2401        /**
2402         * Unfollow a company.
2403         *
2404         * Calling this method causes the current user to stop following the specified
2405         * company, per:
2406         *
2407         *   http://developer.linkedin.com/docs/DOC-1324   
2408         *
2409         * @param str $cid
2410         *    Company ID you want to unfollow. 
2411         *               
2412         * @return arr
2413         *    Array containing retrieval success, LinkedIn response.
2414         */
2415        public function unfollowCompany($cid) {
2416          // check passed data
2417          if(!is_string($cid)) {
2418            // bad data passed
2419                  throw new LinkedInException('LinkedIn->unfollowCompany(): bad data passed, $cid must be of string value.');
2420          }
2421         
2422          // construct and send the request
2423          $query    = self::_URL_API . '/v1/people/~/following/companies/id=' . trim($cid);
2424          $response = $this->fetch('DELETE', $query);
2425         
2426          /**
2427           * Check for successful request (a 204 response from LinkedIn server)
2428           * per the documentation linked in method comments above.
2429           */
2430          return $this->checkResponse(204, $response);
2431        }
2432       
2433        /**
2434         * Unlike a network update.
2435         *     
2436         * Unlike another user's network update:
2437         *
2438         *   http://developer.linkedin.com/docs/DOC-1043
2439         *
2440         * @param str $uid
2441         *    The LinkedIn update ID.
2442         *                       
2443         * @return arr
2444         *    Array containing retrieval success, LinkedIn response.                 
2445         */
2446        public function unlike($uid) {
2447          // check passed data
2448          if(!is_string($uid)) {
2449            // bad data passed
2450                  throw new LinkedInException('LinkedIn->unlike(): bad data passed, $uid must be of type string.');
2451          }
2452   
2453    // construct the xml data
2454                $data = '<?xml version="1.0" encoding="UTF-8"?>
2455                         <is-liked>false</is-liked>';
2456               
2457                // send request
2458    $query    = self::_URL_API . '/v1/people/~/network/updates/key=' . $uid . '/is-liked';
2459    $response = $this->fetch('PUT', $query, $data);
2460   
2461        /**
2462           * Check for successful request (a 201 response from LinkedIn server)
2463           * per the documentation linked in method comments above.
2464           */ 
2465    return $this->checkResponse(201, $response);
2466        }
2467       
2468        /**
2469         * Post network update.
2470         *
2471         * Update the user's Linkedin network status. Full details from LinkedIn
2472         * on this functionality can be found here:
2473         *
2474         *   http://developer.linkedin.com/docs/DOC-1009
2475         *   http://developer.linkedin.com/docs/DOC-1009#comment-1077
2476         *
2477         * @param str $update
2478         *    The network update.       
2479         *
2480         * @return arr
2481         *    Array containing retrieval success, LinkedIn response.             
2482         */
2483        public function updateNetwork($update) {
2484          // check passed data
2485    if(!is_string($update)) {
2486      // nothing/non-string passed, raise an exception
2487                  throw new LinkedInException('LinkedIn->updateNetwork(): bad data passed, $update must be a non-zero length string.');
2488    }
2489   
2490    /**
2491     * Network update is not empty, wrap a cleaned version of it in xml. 
2492     * Network update rules:
2493     *
2494     * 1) No HTML permitted except those found in _NETWORK_HTML constant
2495     * 2) Update cannot be longer than 140 characters.     
2496     */
2497    // get the user data
2498    $response = self::profile('~:(first-name,last-name,site-standard-profile-request)');
2499    if($response['success'] === TRUE) {
2500      /**
2501       * We are converting response to usable data.  I'd use SimpleXML here, but
2502       * to keep the class self-contained, we will use a portable XML parsing
2503       * routine, self::xmlToArray.       
2504       */
2505      $person = self::xmlToArray($response['linkedin']);
2506      if($person === FALSE) {
2507        // bad xml data
2508        throw new LinkedInException('LinkedIn->updateNetwork(): LinkedIn returned bad XML data.');
2509      }
2510                $fields = $person['person']['children'];
2511 
2512                // prepare user data
2513                $first_name   = trim($fields['first-name']['content']);
2514                $last_name    = trim($fields['last-name']['content']);
2515                $profile_url  = trim($fields['site-standard-profile-request']['children']['url']['content']);
2516 
2517      // create the network update
2518      $update = trim(htmlspecialchars(strip_tags($update, self::_NETWORK_HTML)));
2519      if(strlen($update) > self::_NETWORK_LENGTH) {
2520        throw new LinkedInException('LinkedIn->share(): update length is too long - max length is ' . self::_NETWORK_LENGTH . ' characters.');
2521      }
2522      $user   = htmlspecialchars('<a href="' . $profile_url . '">' . $first_name . ' ' . $last_name . '</a>');
2523                $data   = '<activity locale="en_US">
2524                                       <content-type>linkedin-html</content-type>
2525                                       <body>' . $user . ' ' . $update . '</body>
2526                                     </activity>';
2527 
2528      // send request
2529      $query    = self::_URL_API . '/v1/people/~/person-activities';
2530      $response = $this->fetch('POST', $query, $data);
2531     
2532      /**
2533           * Check for successful request (a 201 response from LinkedIn server)
2534           * per the documentation linked in method comments above.
2535           */ 
2536      return $this->checkResponse(201, $response);
2537    } else {
2538      // profile retrieval failed
2539      throw new LinkedInException('LinkedIn->updateNetwork(): profile data could not be retrieved.');
2540    }
2541        }
2542       
2543  /**
2544         * General network update retrieval function.
2545         *
2546         * Takes a string of parameters as input and requests update-related data
2547         * from the Linkedin Network Updates API. See the official documentation for
2548         * $options parameter formatting:
2549         *
2550         *   http://developer.linkedin.com/docs/DOC-1006
2551         *
2552         * For getting more comments, likes, etc, see here:
2553         *
2554         *   http://developer.linkedin.com/docs/DOC-1043                 
2555         *
2556         * @param str $options
2557         *    [OPTIONAL] Data retrieval options.
2558         * @param str $id
2559         *    [OPTIONAL] The LinkedIn ID to restrict the updates for.
2560         *                       
2561         * @return arr
2562         *    Array containing retrieval success, LinkedIn response.
2563         */
2564        public function updates($options = NULL, $id = NULL) {
2565          // check passed data
2566    if(!is_null($options) && !is_string($options)) {
2567            // bad data passed
2568                  throw new LinkedInException('LinkedIn->updates(): bad data passed, $options must be of type string.');
2569          }
2570          if(!is_null($id) && !is_string($id)) {
2571            // bad data passed
2572                  throw new LinkedInException('LinkedIn->updates(): bad data passed, $id must be of type string.');
2573          }
2574         
2575          // construct and send the request
2576          if(!is_null($id) && self::isId($id)) {
2577            $query = self::_URL_API . '/v1/people/' . $id . '/network/updates' . trim($options);
2578          } else {
2579      $query = self::_URL_API . '/v1/people/~/network/updates' . trim($options);
2580    }
2581          $response = $this->fetch('GET', $query);
2582         
2583          /**
2584           * Check for successful request (a 200 response from LinkedIn server)
2585           * per the documentation linked in method comments above.
2586           */
2587          return $this->checkResponse(200, $response);
2588        }
2589       
2590        /**
2591         * Converts passed XML data to an array.
2592         *
2593         * @param str $xml
2594         *    The XML to convert to an array.
2595         *               
2596         * @return arr
2597         *    Array containing the XML data.     
2598         * @return bool
2599         *    FALSE if passed data cannot be parsed to an array.         
2600         */
2601        public static function xmlToArray($xml) {
2602          // check passed data
2603    if(!is_string($xml)) {
2604            // bad data possed
2605      throw new LinkedInException('LinkedIn->xmlToArray(): bad data passed, $xml must be a non-zero length string.');
2606          }
2607         
2608          $parser = xml_parser_create();
2609          xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0);
2610    xml_parser_set_option($parser, XML_OPTION_SKIP_WHITE, 1);
2611    if(xml_parse_into_struct($parser, $xml, $tags)) {
2612            $elements = array();
2613      $stack    = array();
2614      foreach($tags as $tag) {
2615        $index = count($elements);
2616        if($tag['type'] == 'complete' || $tag['type'] == 'open') {
2617          $elements[$tag['tag']]               = array();
2618          $elements[$tag['tag']]['attributes'] = (array_key_exists('attributes', $tag)) ? $tag['attributes'] : NULL;
2619          $elements[$tag['tag']]['content']    = (array_key_exists('value', $tag)) ? $tag['value'] : NULL;
2620          if($tag['type'] == 'open') {
2621            $elements[$tag['tag']]['children'] = array();
2622            $stack[count($stack)] = &$elements;
2623            $elements = &$elements[$tag['tag']]['children'];
2624          }
2625        }
2626        if($tag['type'] == 'close') {
2627          $elements = &$stack[count($stack) - 1];
2628          unset($stack[count($stack) - 1]);
2629        }
2630      }
2631      $return_data = $elements;
2632          } else {
2633            // not valid xml data
2634            $return_data = FALSE;
2635          }
2636          xml_parser_free($parser);
2637    return $return_data;
2638  }
2639}
Note: See TracBrowser for help on using the repository browser.