├── .gitignore ├── .travis.yml ├── LICENSE.txt ├── README.md ├── composer.json ├── phpunit.xml ├── src ├── Blog.php ├── Connectors │ ├── AbstractConnector.php │ ├── ConnectorFactory.php │ ├── ConnectorInterface.php │ ├── Facebook.php │ ├── FacebookGroup.php │ ├── FacebookPage.php │ ├── Linkedin.php │ ├── LinkedinGroup.php │ ├── LinkedinPage.php │ ├── Tumblr.php │ ├── TumblrBlog.php │ └── Twitter.php ├── Exceptions │ ├── AuthorizationException.php │ ├── CSRFException.php │ ├── DuplicatePostException.php │ ├── ExpiredTokenException.php │ ├── GenericPostingException.php │ ├── InvalidConfigurationException.php │ ├── InvalidProviderException.php │ ├── LinkedinForbiddenException.php │ ├── LinkedinPostingException.php │ └── TumblrPostingException.php ├── Group.php ├── Page.php ├── Post.php ├── Profile.php ├── Response.php └── Stats.php └── tests └── Socializr └── Connectors └── ConnectorFactoryTest.php /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor 2 | composer.phar 3 | composer.lock -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - 5.6 5 | - 7.1 6 | 7 | install: 8 | - composer self-update 9 | - composer install --prefer-source --no-interaction --dev 10 | 11 | script: vendor/bin/phpunit 12 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) [year] [fullname] 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/borfast/socializr.svg?branch=master)](https://travis-ci.org/borfast/socializr) 2 | [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/borfast/socializr/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/borfast/socializr/?branch=master) 3 | 4 | HERE BE DRAGONS! 5 | 6 | This is being updated in a hurry and the code is ugly, so don't expect to be 7 | able to use this easily for your own project, because for now it's not meant to 8 | be used in anything other than the project I'm working on. 9 | 10 | This is a very simple PHP library for posting to several social networks, started out 11 | of frustration for not finding anything like it besides Hybridauth, which 12 | doesn't really suite my needs or quality requirements. 13 | 14 | For now it only supports Facebook, Twitter and LinkedIn. More networks will come soon. 15 | 16 | Built in a hurry (very few tests, few comments) because its meant for a product 17 | that has been delayed due to non-stop issues with Hybridauth. I will eventually 18 | improve this. 19 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "borfast/socializr", 3 | "description": "Simple PHP library for posting to several social networks.", 4 | "type": "library", 5 | "license": "MIT", 6 | "authors": [ 7 | { 8 | "name": "Raúl Santos", 9 | "homepage": "https://github.com/borfast/socializr" 10 | } 11 | ], 12 | "require": { 13 | "php": ">=5.4", 14 | "lusitanian/oauth": "dev-patch-1", 15 | "guzzlehttp/guzzle": "~4" 16 | }, 17 | "repositories": [ 18 | { 19 | "type": "vcs", 20 | "url": "git@github.com:milicevic79/PHPoAuthLib.git" 21 | } 22 | ], 23 | "require-dev": { 24 | "phpunit/phpunit": "~4", 25 | "mockery/mockery": "dev-master" 26 | }, 27 | "autoload": { 28 | "psr-4": { 29 | "Borfast\\Socializr\\": "src" 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 15 | ./tests/ 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/Blog.php: -------------------------------------------------------------------------------- 1 | 'key_in_attributes_array'] 25 | * 26 | * @param array $mapping 27 | * @param array $attributes 28 | * @return static 29 | */ 30 | public static function create(array $mapping, array $attributes) 31 | { 32 | $blog = new Blog; 33 | 34 | array_walk($mapping, function (&$name, $key) use (&$blog, &$attributes) { 35 | $blog->$key = (isset($attributes[$name])) ? $attributes[$name] : null; 36 | }); 37 | 38 | return $blog; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Connectors/AbstractConnector.php: -------------------------------------------------------------------------------- 1 | config = $config; 30 | $this->service = $service; 31 | $this->options = $options; 32 | $this->id = $id; 33 | static::$provider = $service->service(); 34 | } 35 | 36 | 37 | public function request($path, $method = 'GET', $params = [], $headers = []) 38 | { 39 | if (empty($params)) { 40 | $params = null; 41 | } 42 | 43 | try { 44 | $result = $this->service->request($path, $method, $params, $headers); 45 | } catch (OauthExpiredTokenException $e) { 46 | throw new ExpiredTokenException(); 47 | } catch (TokenNotFoundException $e) { 48 | throw new AuthorizationException(); 49 | } 50 | 51 | return $result; 52 | } 53 | 54 | 55 | /** 56 | * The method that initiates the provider authentication process. 57 | * It returns the provider's authentication/login page, which in turn 58 | * will redirect back to us. We don't do the redirect ourselves because that 59 | * means changing the application workflow and we don't want to get in the 60 | * way of how people do things. 61 | * 62 | * @todo Use pluggable\swappable CSRF token storage. 63 | */ 64 | public function getAuthorizationUri(array $params = []) 65 | { 66 | // Check if this provider uses an CSRF token at all. 67 | if (!empty($this->config['csrf_token_name'])) { 68 | // Generate a random anti-CSRF token. 69 | $csrf_token = base64_encode(openssl_random_pseudo_bytes(32)); 70 | 71 | // Write our token in session so we can check it after auth. 72 | // // TODO: This could be improved and make the session storage 73 | // pluggable. Is it worth the trouble, though? 74 | session_start(); 75 | $_SESSION['socializr_csrf_token'] = $csrf_token; 76 | session_write_close(); 77 | 78 | // Add the CSRF token to the request. 79 | $csrf_token_name = $this->config['csrf_token_name']; 80 | $params = array_merge($params, [$csrf_token_name => $csrf_token]); 81 | } 82 | 83 | $url = $this->service->getAuthorizationUri($params); 84 | return $url; 85 | } 86 | 87 | 88 | /** 89 | * For when the OAuth token expires and we need to refresh it. 90 | */ 91 | public function refreshAccessToken() 92 | { 93 | try { 94 | $token = $this->service->getStorage()->retrieveAccessToken(static::$provider); 95 | } catch (TokenNotFoundException $e) { 96 | throw new AuthorizationException(); 97 | } 98 | $this->service->refreshAccessToken($token); 99 | } 100 | 101 | 102 | /** 103 | * Check that the CSRF token 104 | */ 105 | public function checkCsrf(array $get) 106 | { 107 | // Check if this provider uses an CSRF token at all. 108 | if (!empty($this->config['csrf_token_name'])) { 109 | 110 | session_start(); 111 | 112 | // If we don't have a token and should have one, crash and burn. 113 | if (!isset($_SESSION['socializr_csrf_token'])) { 114 | throw new CSRFException('No CSRF token stored. Possible CSRF attack.', 1); 115 | } 116 | 117 | $stored_token = $_SESSION['socializr_csrf_token']; 118 | session_write_close(); 119 | 120 | // Now get the token from the URL 121 | $csrf_token_name = $this->config['csrf_token_name']; 122 | $received_token = $get[$csrf_token_name]; 123 | 124 | // Finally check that the stored token and the received token match. 125 | if (strcmp($stored_token, $received_token) != 0) { 126 | throw new CSRFException('Verification code mismatch. Possible CSRF attack.', 1); 127 | } 128 | } 129 | } 130 | 131 | 132 | public function getSessionData() 133 | { 134 | try { 135 | return $this->service->getStorage()->retrieveAccessToken(static::$provider)->getAccessToken(); 136 | } catch (TokenNotFoundException $e) { 137 | throw new AuthorizationException(); 138 | } 139 | } 140 | 141 | 142 | public function get($path, $params = []) 143 | { 144 | $response = json_decode( 145 | $this->service->request($path, 'GET', $params), 146 | true 147 | ); 148 | 149 | return $response; 150 | } 151 | 152 | 153 | /** 154 | * The method that sets the OAuth token for the current provider. It must be 155 | * called after the authorize() method. Retrieves the auth token from the 156 | * provider's response and store it. 157 | * 158 | * @params array $params The URL params. Each Connector knows how to get the 159 | * token for its specific provider. 160 | */ 161 | public function storeOauthToken($params) 162 | { 163 | $this->service->requestAccessToken($params['code']); 164 | } 165 | 166 | 167 | public function getUid() 168 | { 169 | if (is_null($this->id)) { 170 | $profile = $this->getProfile(); 171 | $this->id = $profile->id; 172 | } 173 | 174 | return $this->id; 175 | } 176 | 177 | 178 | // These should be implementation-specific. 179 | public function getProfile() 180 | { 181 | throw new \Exception('Trying to get a Profile from a generic provider. This probably means you are trying to get a type of data that does not make sense for the connector you are using. For example, trying to get a Facebook Profile from a FacebookPage connector.'); 182 | } 183 | 184 | public function getPage() 185 | { 186 | throw new \Exception('Trying to get a Page from a generic provider. This probably means you are trying to get a type of data that does not make sense for the connector you are using. For example, trying to get a Facebook Page from a FacebookGroup connector.'); 187 | } 188 | 189 | public function getPages() 190 | { 191 | throw new \Exception('Trying to get Pages from a generic provider. This probably means you are trying to get a type of data that does not make sense for the connector you are using.'); 192 | } 193 | 194 | public function getGroup() 195 | { 196 | throw new \Exception('Trying to get a Group from a generic provider. This probably means you are trying to get a type of data that does not make sense for the connector you are using. For example, trying to get a Facebook Group from a FacebookPage connector.'); 197 | } 198 | 199 | public function getGroups() 200 | { 201 | throw new \Exception('Trying to get Groups from a generic provider. This probably means you are trying to get a type of data that does not make sense for the connector you are using.'); 202 | } 203 | 204 | abstract public function post(Post $post); 205 | abstract public function getStats(); 206 | } 207 | -------------------------------------------------------------------------------- /src/Connectors/ConnectorFactory.php: -------------------------------------------------------------------------------- 1 | config = $config; 41 | 42 | if (!array_key_exists('providers', $config)) { 43 | throw new InvalidConfigurationException; 44 | } 45 | } 46 | 47 | 48 | /** 49 | * Creates a Connector object for the given provider type. The $id parameter 50 | * may be null but should only be used like that when getting 51 | * 52 | * @param string $provider The provider type you want. 53 | * @param TokenStorageInterface $storage The storage for PHPoAuthLib. 54 | * @param array $options 55 | * @param string $id The ID we're connecting to. 56 | * @param null|ClientInterface $http_client The HTTP client for PHPoAuthLib. 57 | * @param null|ServiceFactory $service_factory The PHPoAuthLib service factory. 58 | * @param null|CredentialsInterface $credentials The credentials for PHPoAuthLib. 59 | * @return ConnectorInterface An instance of the requested connector type. 60 | * @throws InvalidProviderException 61 | */ 62 | public function createConnector( 63 | $provider, 64 | TokenStorageInterface $storage, 65 | array $options = [], 66 | $id = null, 67 | ClientInterface $http_client = null, 68 | ServiceFactory $service_factory = null, 69 | CredentialsInterface $credentials = null 70 | ) { 71 | // Only allow configured providers. 72 | if (!array_key_exists($provider, $this->config['providers'])) { 73 | throw new InvalidProviderException($provider); 74 | } 75 | 76 | // Default to CurlClient (why isn't this the default? :( ) 77 | if (is_null($http_client)) { 78 | $http_client = new CurlClient; 79 | } 80 | 81 | // Just if we want to be lazy and not pass this as an argument. 82 | if (is_null($service_factory)) { 83 | $service_factory = new ServiceFactory; 84 | } 85 | 86 | // Simplify config access for this provider. 87 | $config = $this->getFlatConfig($provider); 88 | 89 | 90 | // We're already getting the credentials via $this->config, we might not 91 | // want to always pass them as an argument. 92 | if (is_null($credentials)) { 93 | $credentials = new Credentials( 94 | $config['consumer_key'], 95 | $config['consumer_secret'], 96 | $config['callback'] 97 | ); 98 | } 99 | 100 | // Let's make use of CurlClient. 101 | $service_factory->setHttpClient($http_client); 102 | 103 | // Temporary (or so I hope) hack to overcome PHPoAuthLib not being ready 104 | // for Facebook's Graph API 1.0 deprecation. 105 | 106 | // If this is Facebook, let's specify we want API v2.2 107 | // $api_version = null; 108 | // if (strtolower($provider) == 'facebook') { 109 | // $api_version = '2.2'; 110 | // } 111 | $uri = null; 112 | if (in_array($provider, ['Facebook', 'FacebookGroup', 'FacebookPage'])) { 113 | $uri = new Uri('https://graph.facebook.com/v2.8/'); 114 | } 115 | 116 | // Finally, create the service already! 117 | $service = $service_factory->createService( 118 | $config['service'], 119 | $credentials, 120 | $storage, 121 | $config['scopes'], 122 | $uri 123 | //$api_version 124 | ); 125 | 126 | 127 | $connector_class = '\\Borfast\\Socializr\\Connectors\\'.$provider; 128 | $connector = new $connector_class($config, $service, $options, $id); 129 | 130 | return $connector; 131 | } 132 | 133 | 134 | /** 135 | * Gets a config array for the given provider, taking care of a few checks 136 | * to make sure it has the needed data. 137 | * @param string $provider The provider type we want the config for. 138 | * @return array The config for the requested provider type. 139 | */ 140 | protected function getFlatConfig($provider) 141 | { 142 | $config = $this->config['providers'][$provider]; 143 | 144 | /* 145 | * Make sure we will create the correct PHPoAuthLib service. Each 146 | * configured provider can specify which service to use. If none is 147 | * specified, then the provider name is used. 148 | */ 149 | if (empty($config['service'])) { 150 | $config['service'] = $provider; 151 | } 152 | 153 | // Cater for the possibility of having one single general callback URL. 154 | if (empty($config['callback'])) { 155 | $config['callback'] = $this->config['callback']; 156 | } 157 | 158 | // Cater for the possibility of no scope being defined 159 | if (empty($config['scopes'])) { 160 | $config['scopes'] = []; 161 | } 162 | 163 | // Make it possible to define the scopes as a comma separated string 164 | // instead of an array. 165 | if (!is_array($config['scopes'])) { 166 | $config['scopes'] = explode(', ', $config['scopes']); 167 | } 168 | 169 | return $config; 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /src/Connectors/ConnectorInterface.php: -------------------------------------------------------------------------------- 1 | title; 71 | $msg .= "\n\n"; 72 | $msg .= $post->body; 73 | $msg = trim($msg); 74 | 75 | if (empty($post->media)) { 76 | $path = '/'.$this->getProfile()->id.'/feed'; 77 | 78 | $params = [ 79 | // 'caption' => $post->title, 80 | 'description' => '', 81 | 'link' => $post->url, 82 | 'message' => $msg 83 | ]; 84 | } else { 85 | $path = '/'.$this->getProfile()->id.'/photos'; 86 | 87 | $msg .= "\n"; 88 | $msg .= $post->url; 89 | 90 | $params = [ 91 | 'url' => $post->media[0], 92 | 'caption' => $msg 93 | ]; 94 | } 95 | 96 | $method = 'POST'; 97 | 98 | $result = $this->request($path, $method, $params); 99 | 100 | $json_result = json_decode($result, true); 101 | 102 | // If there's no ID, the post didn't go through 103 | if (!isset($json_result['id'])) { 104 | $msg = "Unknown error posting to Facebook profile."; 105 | throw new GenericPostingException($msg, 1); 106 | } 107 | 108 | $response = new Response; 109 | $response->setRawResponse($result); // This is already JSON. 110 | $response->setProvider('Facebook'); 111 | $response->setPostId($json_result['id']); 112 | 113 | return $response; 114 | } 115 | 116 | public function getProfile() 117 | { 118 | if (is_null($this->profile)) { 119 | $path = '/me'; 120 | $result = $this->request($path); 121 | $json_result = json_decode($result, true); 122 | 123 | $mapping = [ 124 | 'id' => 'id', 125 | 'email' => 'email', 126 | 'name' => 'name', 127 | 'first_name' => 'first_name', 128 | 'middle_name' => 'middle_name', 129 | 'last_name' => 'last_name', 130 | 'username' => 'username', 131 | // 'username' => 'email', // Facebook Graph API 2.0 doesn't have username 132 | 'link' => 'link' 133 | ]; 134 | 135 | $this->profile = Profile::create($mapping, $json_result); 136 | $this->profile->provider = static::$provider; 137 | $this->profile->raw_response = $result; 138 | } 139 | 140 | return $this->profile; 141 | } 142 | 143 | public function getPermissions() 144 | { 145 | $profile = $this->getProfile(); 146 | 147 | $path = '/'.$profile->id.'/permissions'; 148 | return $this->request($path); 149 | } 150 | 151 | public function getStats() 152 | { 153 | return $this->getFriendsCount(); 154 | } 155 | 156 | public function getPages() 157 | { 158 | $profile = $this->getProfile(); 159 | 160 | $path = '/'.$profile->id.'/accounts?fields=name,picture,access_token,id,can_post,likes,link,username'; 161 | $result = $this->request($path); 162 | $json_result = json_decode($result, true); 163 | 164 | $pages = []; 165 | 166 | $mapping = [ 167 | 'id' => 'id', 168 | 'name' => 'name', 169 | 'link' => 'link', 170 | 'can_post' => 'can_post', 171 | 'access_token' => 'access_token' 172 | ]; 173 | 174 | // Make the page IDs available as the array keys 175 | if (!empty($json_result['data'])) { 176 | foreach ($json_result['data'] as $page) { 177 | $pages[$page['id']] = Page::create($mapping, $page); 178 | $pages[$page['id']]->picture = $page['picture']['data']['url']; 179 | $pages[$page['id']]->provider = static::$provider; 180 | $pages[$page['id']]->raw_response = $result; 181 | } 182 | } 183 | 184 | return $pages; 185 | } 186 | 187 | public function getGroups() 188 | { 189 | $profile = $this->getProfile(); 190 | 191 | $path = '/'.$profile->id.'/groups?fields=id,name,icon'; 192 | $result = $this->request($path); 193 | $json_result = json_decode($result, true); 194 | 195 | $groups = []; 196 | 197 | $mapping = [ 198 | 'id' => 'id', 199 | 'name' => 'name', 200 | 'picture' => 'icon' 201 | ]; 202 | 203 | // Make the group IDs available as the array keys 204 | if (!empty($json_result['data'])) { 205 | foreach ($json_result['data'] as $group) { 206 | $groups[$group['id']] = Group::create($mapping, $group); 207 | $groups[$group['id']]->picture = $group['icon']; 208 | $groups[$group['id']]->link = 'https://www.facebook.com/groups/' . $group['id']; 209 | $groups[$group['id']]->can_post = true; 210 | $groups[$group['id']]->provider = static::$provider; 211 | $groups[$group['id']]->raw_response = $result; 212 | } 213 | } 214 | 215 | return $groups; 216 | } 217 | 218 | /**************************************************** 219 | * 220 | * From here on these are Facebook-specific methods. 221 | * 222 | ***************************************************/ 223 | public function getFriendsCount() 224 | { 225 | $path = '/'.$this->getProfile()->id.'/friends'; 226 | $result = $this->request($path); 227 | 228 | $response = json_decode($result); 229 | 230 | if (property_exists($response, 'summary')) { 231 | $response = $response->summary->total_count; 232 | } else { 233 | $response = '-'; 234 | } 235 | 236 | return $response; 237 | } 238 | } 239 | -------------------------------------------------------------------------------- /src/Connectors/FacebookGroup.php: -------------------------------------------------------------------------------- 1 | getGroup(); 20 | 21 | if (empty($post->media)) { 22 | $path = '/'.$group->id.'/feed'; 23 | 24 | $msg = $post->title; 25 | $msg .= "\n\n"; 26 | $msg .= $post->body; 27 | $msg = trim($msg); 28 | 29 | $params = [ 30 | // 'caption' => $post->title, 31 | 'description' => '', 32 | 'link' => $post->url, 33 | 'message' => $msg 34 | ]; 35 | } else { 36 | $path = '/'.$group->id.'/photos'; 37 | 38 | $msg = $post->title; 39 | $msg .= "\n\n"; 40 | $msg .= $post->body; 41 | $msg .= "\n"; 42 | $msg .= $post->url; 43 | 44 | $params = [ 45 | 'url' => $post->media[0], 46 | 'caption' => $msg 47 | ]; 48 | } 49 | 50 | $method = 'POST'; 51 | 52 | $result = $this->request($path, $method, $params); 53 | $json_result = json_decode($result, true); 54 | 55 | // If there's no ID, the post didn't go through 56 | if (!isset($json_result['id'])) { 57 | $msg = "Unknown error posting to Facebook group."; 58 | throw new GenericPostingException($msg, 1); 59 | } 60 | 61 | $response = new Response; 62 | $response->setRawResponse($result); // This is already JSON. 63 | $response->setProvider('Facebook'); 64 | $response->setPostId($json_result['id']); 65 | 66 | return $response; 67 | } 68 | 69 | public function getGroup() 70 | { 71 | if (is_null($this->group)) { 72 | $path = '/' . $this->id . '?fields=id,name,icon'; 73 | $result = $this->request($path); 74 | $json_result = json_decode($result, true); 75 | 76 | $mapping = [ 77 | 'id' => 'id', 78 | 'name' => 'name', 79 | 'picture' => 'icon' 80 | ]; 81 | 82 | $this->group = Group::create($mapping, $json_result); 83 | $this->group->picture = $json_result['icon']; 84 | $this->group->link = 'https://www.facebook.com/groups/' . $json_result['id']; 85 | $this->group->can_post = true; 86 | $this->group->provider = static::$provider; 87 | $this->group->raw_response = $result; 88 | } 89 | 90 | return $this->group; 91 | } 92 | 93 | /** 94 | * Get the number of memebers this group has. 95 | */ 96 | public function getStats() 97 | { 98 | return $this->getMembersCount(); 99 | } 100 | 101 | 102 | /*************************************************************************** 103 | * 104 | * From here on these are FacebookGroup-specific methods that should not be 105 | * accessed from other classes. 106 | * 107 | **************************************************************************/ 108 | 109 | protected function getMembersCount() 110 | { 111 | $group = $this->getGroup(); 112 | 113 | $path = '/'.$group->id.'/members'; 114 | $result = $this->request($path); 115 | 116 | $response = json_decode($result); 117 | $response = count($response->data); 118 | 119 | return $response; 120 | } 121 | 122 | } 123 | -------------------------------------------------------------------------------- /src/Connectors/FacebookPage.php: -------------------------------------------------------------------------------- 1 | getPage(); 18 | 19 | if (empty($post->media)) { 20 | $path = '/'.$page->id.'/feed'; 21 | $access_token = $post->options['page_access_token']; 22 | 23 | $msg = $post->title; 24 | $msg .= "\n\n"; 25 | $msg .= $post->body; 26 | $msg = trim($msg); 27 | 28 | $params = [ 29 | // 'caption' => $post->title, 30 | 'description' => '', 31 | 'link' => $post->url, 32 | 'message' => $msg, 33 | 'access_token' => $access_token 34 | ]; 35 | } else { 36 | $path = '/'.$page->id.'/photos'; 37 | 38 | $msg = $post->title; 39 | $msg .= "\n\n"; 40 | $msg .= $post->body; 41 | $msg .= "\n"; 42 | $msg .= $post->url; 43 | 44 | $params = [ 45 | 'url' => $post->media[0], 46 | 'caption' => $msg 47 | ]; 48 | } 49 | 50 | $method = 'POST'; 51 | 52 | $result = $this->request($path, $method, $params); 53 | $json_result = json_decode($result, true); 54 | 55 | // If there's no ID, the post didn't go through 56 | if (!isset($json_result['id'])) { 57 | $msg = "Unknown error posting to Facebook page."; 58 | throw new GenericPostingException($msg, 1); 59 | } 60 | 61 | $response = new Response; 62 | $response->setRawResponse($result); 63 | $response->setProvider('Facebook'); 64 | $response->setPostId($json_result['id']); 65 | 66 | return $response; 67 | } 68 | 69 | public function getPage() 70 | { 71 | if (is_null($this->page)) { 72 | $path = '/'.$this->id.'?fields=id,name,picture,access_token,can_post,likes,fan_count,link,username'; 73 | $result = $this->request($path); 74 | $json_result = json_decode($result, true); 75 | 76 | $mapping = [ 77 | 'id' => 'id', 78 | 'name' => 'name', 79 | 'link' => 'link', 80 | 'can_post' => 'can_post', 81 | 'access_token' => 'access_token', 82 | 'likes' => 'likes', 83 | 'fan_count' => 'fan_count' 84 | ]; 85 | 86 | $this->page = Page::create($mapping, $json_result); 87 | $this->page->provider = static::$provider; 88 | $this->page->raw_response = $result; 89 | } 90 | 91 | return $this->page; 92 | } 93 | 94 | 95 | /** 96 | * Get the number of likes this page has. 97 | */ 98 | public function getStats() 99 | { 100 | return $this->getLikesCount(); 101 | } 102 | 103 | 104 | /*************************************************************************** 105 | * 106 | * From here on these are FacebookPage-specific methods that should not be 107 | * accessed from other classes. 108 | * 109 | **************************************************************************/ 110 | 111 | protected function getLikesCount() 112 | { 113 | return $this->getPage()->fan_count; 114 | } 115 | 116 | 117 | public function addTab($page_id, $page_access_token, $app_id, array $params = []) 118 | { 119 | $path = '/'.$page_id.'/tabs'; 120 | $method = 'POST'; 121 | $static_params = [ 122 | 'app_id' => $app_id, 123 | 'access_token' => $page_access_token 124 | ]; 125 | 126 | $params = array_merge($static_params, $params); 127 | 128 | $response = $this->request($path, $method, $params); 129 | $response = json_decode($response); 130 | 131 | return $response; 132 | } 133 | 134 | 135 | public function getTabs($page_id, $page_access_token, $app_id) 136 | { 137 | $path = '/'.$page_id.'/tabs'; 138 | $path .= '?access_token='.$page_access_token; 139 | $method = 'GET'; 140 | 141 | $response = $this->request($path, $method); 142 | $response = json_decode($response); 143 | 144 | return $response; 145 | } 146 | 147 | 148 | public function getTab($page_id, $page_access_token, $app_id) 149 | { 150 | $path = '/'.$page_id.'/tabs/app_'.$app_id; 151 | $path .= '?access_token='.$page_access_token; 152 | $method = 'GET'; 153 | 154 | $response = $this->request($path, $method); 155 | $response = json_decode($response); 156 | 157 | return $response; 158 | } 159 | 160 | 161 | public function updateTab($page_id, $page_access_token, $app_id, array $params) 162 | { 163 | $path = '/'.$page_id.'/tabs/app_'.$app_id; 164 | $method = 'POST'; 165 | $params['access_token'] = $page_access_token; 166 | 167 | $response = $this->request($path, $method, $params); 168 | $response = json_decode($response); 169 | 170 | return $response; 171 | } 172 | 173 | 174 | public function removeTab($page_id, $page_access_token, $app_id) 175 | { 176 | $path = '/'.$page_id.'/tabs/app_'.$app_id; 177 | $path .= '?access_token='.$page_access_token; 178 | $method = 'DELETE'; 179 | $params = [ 180 | 'app_id' => $app_id, 181 | 'access_token' => $page_access_token 182 | ]; 183 | 184 | $response = $this->request($path, $method, $params); 185 | $response = json_decode($response); 186 | 187 | return $response; 188 | } 189 | } 190 | -------------------------------------------------------------------------------- /src/Connectors/Linkedin.php: -------------------------------------------------------------------------------- 1 | [ 53 | 'code' => 'anyone' 54 | ], 55 | 'comment' => '', 56 | 'content' => [ 57 | 'title' => $post->title, 58 | 'submitted-url' => $post->url, 59 | 'description' => $post->body, 60 | ] 61 | ]; 62 | 63 | if (!empty($post->media)) { 64 | $params['content']['submitted-image-url'] = $post->media[0]; 65 | } 66 | 67 | $params = json_encode($params); 68 | 69 | $result = $this->request($path, $method, $params); 70 | 71 | $response = new Response; 72 | $response->setRawResponse($result); // This is already JSON. 73 | $response->setProvider(static::$provider); 74 | $result_json = json_decode($result); 75 | $response->setPostId($result_json->updateKey); 76 | $response->setPostUrl($result_json->updateUrl); 77 | 78 | return $response; 79 | } 80 | 81 | 82 | public function getProfile() 83 | { 84 | $path = '/people/~:(id,first-name,last-name,maiden-name,public-profile-url,formatted-name,num-connections,email-address,num-recommenders)?format=json'; 85 | $result = $this->request($path); 86 | $json_result = json_decode($result, true); 87 | 88 | $mapping = [ 89 | 'id' => 'id', 90 | 'email' => 'emailAddress', 91 | 'name' => 'formattedName', 92 | 'first_name' => 'firstName', 93 | 'middle_name' => 'maidenName', 94 | 'last_name' => 'lastName', 95 | // 'username' => 'username', 96 | 'link' => 'publicProfileUrl', 97 | 'likes' => 'numConnections' 98 | ]; 99 | 100 | $profile = Profile::create($mapping, $json_result); 101 | $profile->provider = static::$provider; 102 | $profile->raw_response = $result; 103 | 104 | return $profile; 105 | } 106 | 107 | public function getStats() 108 | { 109 | $profile = $this->getProfile(); 110 | 111 | return $profile->likes; 112 | } 113 | 114 | public function getPermissions() 115 | { 116 | return null; 117 | } 118 | 119 | public function getPages() 120 | { 121 | $path = '/companies:(id,name,universal-name,square-logo-url,num-followers)?is-company-admin=true&format=json'; 122 | $result = $this->request($path); 123 | $json_result = json_decode($result, true); 124 | 125 | $pages = []; 126 | 127 | $mapping = [ 128 | 'id' => 'id', 129 | 'name' => 'name', 130 | 'picture' => 'squareLogoUrl', 131 | 'link' => 'publicProfileUrl' 132 | ]; 133 | 134 | // Make th epage IDs available as the array keys and get their picture 135 | if (!empty($json_result['values'])) { 136 | foreach ($json_result['values'] as $company) { 137 | $pages[$company['id']] = Page::create($mapping, $company); 138 | $pages[$company['id']]->link = 'http://www.linkedin.com/company/'.$company['universalName']; 139 | $pages[$company['id']]->provider = static::$provider; 140 | $pages[$company['id']]->raw_response = $result; 141 | } 142 | } 143 | 144 | return $pages; 145 | } 146 | 147 | 148 | public function getGroups() 149 | { 150 | $path = '/people/~/group-memberships:(group:(id,name,site-group-url,small-logo-url,num-members,relation-to-viewer))?&format=json&count=999'; 151 | $response = $this->request($path); 152 | $groups = json_decode($response, true); 153 | 154 | $group_pages = []; 155 | 156 | $mapping = [ 157 | 'id' => 'id', 158 | 'name' => 'name', 159 | 'picture' => 'smallLogoUrl', 160 | 'link' => 'siteGroupUrl' 161 | ]; 162 | 163 | // Make the page IDs available as the array keys and get their picture 164 | if (!empty($groups['values'])) { 165 | foreach ($groups['values'] as $group) { 166 | $group_pages[$group['_key']] = Group::create($mapping, $group['group']); 167 | $group_pages[$group['_key']]->provider = static::$provider; 168 | $group_pages[$group['_key']]->raw_response = $response; 169 | 170 | // Let's check if our user can post to this group. 171 | // Thank you for this wonder, LinkedIn! It's so fun parsing infinitely nested arrays... 172 | $actions = $group['group']['relationToViewer']['availableActions']['values']; 173 | array_walk($actions, function ($value) use ($group, $group_pages) { 174 | if ($value['code'] === 'add-post') { 175 | $group_pages[$group['_key']]->can_post = true; 176 | } 177 | }); 178 | } 179 | } 180 | 181 | return $group_pages; 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /src/Connectors/LinkedinGroup.php: -------------------------------------------------------------------------------- 1 | options['group_id']; 28 | 29 | try { 30 | $token = $this->service->getStorage()->retrieveAccessToken('Linkedin')->getAccessToken(); 31 | } catch (TokenNotFoundException $e) { 32 | throw new AuthorizationException(); 33 | } 34 | 35 | $path = '/groups/'.$group_id.'/posts?format=json&oauth2_access_token='.$token; 36 | $params = [ 37 | 'title' => $post->title, 38 | 'summary' => '', 39 | 'content' => [ 40 | 'title' => $post->title . ' @', 41 | 'submitted-url' => $post->url, 42 | 'description' => $post->body, 43 | ], 44 | ]; 45 | 46 | // Add media files, if they were sent. 47 | if (isset($post->media) && array_key_exists(0, $post->media)) { 48 | $params['content']['submitted-image-url'] = $post->media[0]; 49 | } 50 | 51 | $params = json_encode($params); 52 | 53 | $url = 'https://api.linkedin.com/v1'.$path; 54 | // Linkedin API requires the Content-Type header set to application/json 55 | $options = [ 56 | 'headers' => ['Content-Type' => 'application/json'], 57 | 'body' => $params 58 | ]; 59 | 60 | $client = new Guzzle(); 61 | try { 62 | $result = $client->post($url, $options); 63 | } catch (ClientException $e) { 64 | if ($e->getCode() >= 400) { 65 | throw new LinkedinForbiddenException($e); 66 | } else { 67 | throw $e; 68 | } 69 | } 70 | 71 | if ($result->getStatusCode() > 300) { 72 | $msg = "Error posting to Linkedin group. Error code from Linkedin: %s. Error message from Linkedin: %s"; 73 | $msg = sprintf($msg, $result->status_code, json_decode($result->body)->message); 74 | throw new LinkedinPostingException($msg, $result->status_code); 75 | } 76 | 77 | $response = new Response; 78 | $response->setRawResponse($result); // This is already JSON. 79 | $response->setProvider(static::$provider); 80 | //$response->setPostId($result->getHeader('x-li-uuid')); 81 | 82 | // As amazing as it may sound, there's a three year old bug that LinkedIn 83 | // knows of but doesn't fix, which is simply the group posts URL is not 84 | // returned when we create the post, and when the post endpoint is queried 85 | // it returns a URL containing an incorrect domain: api.linkedin.com 86 | // instead of www.linkedin.com. They acknowledge this in the "Known Issues" 87 | // section of the groups API documentation and say the workaround is simple: 88 | // just swap the domains. Well, thanks for nothing. Would it be so hard for 89 | // them to return a public URL along with the response of the creation?... 90 | // So we need to make another API call to fetch the correct URL, because 91 | // it's not even possible to generate it manually. 92 | 93 | // Moderated groups don't return a 'location' header, so let's skip it if that's the case. 94 | $location = $result->getHeader('Location'); 95 | if (!empty($location)) { 96 | $url = $location . ':(id,site-group-post-url)?format=json&oauth2_access_token=' . $token; 97 | $result = $client->get($url); 98 | $json = $result->json(); 99 | 100 | $post_url = str_replace('api.linkedin.com/v1', 'www.linkedin.com', $json['siteGroupPostUrl']); 101 | $response->setPostUrl($post_url); 102 | } 103 | 104 | return $response; 105 | } 106 | 107 | 108 | public function getProfile() 109 | { 110 | $path = '/people/~:(id,first-name,last-name,maiden-name,public-profile-url,formatted-name,num-connections,email-address,num-recommenders)?format=json'; 111 | $response = $this->service->request($path); 112 | $profile_json = json_decode($response, true); 113 | 114 | $mapping = [ 115 | 'id' => 'id', 116 | 'email' => 'emailAddress', 117 | 'name' => 'formattedName', 118 | 'first_name' => 'firstName', 119 | 'middle_name' => 'maidenName', 120 | 'last_name' => 'lastName', 121 | // 'username' => 'username', 122 | 'link' => 'publicProfileUrl' 123 | ]; 124 | 125 | $profile = Profile::create($mapping, $profile_json); 126 | $profile->provider = static::$provider; 127 | $profile->raw_response = $response; 128 | 129 | return $profile; 130 | } 131 | 132 | public function getStats() 133 | { 134 | $path = 'groups/'.$this->id.':(id,num-members)?format=json'; 135 | $response = json_decode($this->request($path)); 136 | 137 | return $response->numMembers; 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /src/Connectors/LinkedinPage.php: -------------------------------------------------------------------------------- 1 | options['page_id']; 21 | $path = '/companies/'.$page_id.'/shares?format=json'; 22 | $method = 'POST'; 23 | $params = [ 24 | 'visibility' => [ 25 | 'code' => 'anyone' 26 | ], 27 | 'comment' => '', 28 | 'content' => [ 29 | 'title' => $post->title, 30 | 'submitted-url' => $post->url, 31 | 'description' => $post->body, 32 | ], 33 | ]; 34 | 35 | if (!empty($post->media)) { 36 | $params['content']['submitted-image-url'] = $post->media[0]; 37 | } 38 | 39 | $params = json_encode($params); 40 | 41 | // Linkedin API requires the Content-Type header set to application/json 42 | $header = ['Content-Type' => 'application/json']; 43 | $result = $this->request($path, $method, $params, $header); 44 | 45 | // The response comes in JSON 46 | $json_result = json_decode($result, true); 47 | 48 | if (isset($json_result['status']) && $json_result['status'] != 200) { 49 | $msg = "Error posting to Linkedin page. Error code from Linkedin: %s. Error message from Linkedin: %s"; 50 | $msg = sprintf($msg, $json_result['errorCode'], $json_result['message']); 51 | 52 | if ($json_result['status'] == '401') { 53 | throw new ExpiredTokenException($msg); 54 | } else { 55 | throw new GenericPostingException($msg, $json_result['status']); 56 | } 57 | } 58 | 59 | $response = new Response; 60 | $response->setRawResponse(json_encode($result)); 61 | $response->setProvider(static::$provider); 62 | $result_json = json_decode($result); 63 | $response->setPostId($result_json->updateKey); 64 | $response->setPostUrl($result_json->updateUrl); 65 | 66 | return $response; 67 | } 68 | 69 | 70 | public function getProfile() 71 | { 72 | $path = '/people/~:(id,first-name,last-name,maiden-name,public-profile-url,formatted-name,num-connections,email-address,num-recommenders)?format=json'; 73 | $response = $this->service->request($path); 74 | $profile_json = json_decode($response, true); 75 | 76 | $mapping = [ 77 | 'id' => 'id', 78 | 'email' => 'emailAddress', 79 | 'name' => 'formattedName', 80 | 'first_name' => 'firstName', 81 | 'middle_name' => 'maidenName', 82 | 'last_name' => 'lastName', 83 | // 'username' => 'username', 84 | 'link' => 'publicProfileUrl' 85 | ]; 86 | 87 | $profile = Profile::create($mapping, $profile_json); 88 | $profile->provider = static::$provider; 89 | $profile->raw_response = $response; 90 | 91 | return $profile; 92 | } 93 | 94 | public function getStats() 95 | { 96 | $path = 'companies/'.$this->id.':(id,num-followers)?format=json'; 97 | $response = json_decode($this->request($path)); 98 | 99 | return $response->numFollowers; 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/Connectors/Tumblr.php: -------------------------------------------------------------------------------- 1 | service->requestRequestToken(); 32 | $extra = ['oauth_token' => $token->getRequestToken()]; 33 | return parent::getAuthorizationUri($extra); 34 | } 35 | 36 | 37 | /** 38 | * Retrieve the auth token from the provider's response and store it. 39 | */ 40 | public function storeOauthToken($params) 41 | { 42 | try { 43 | $token = $this->service->getStorage()->retrieveAccessToken('Tumblr'); 44 | } catch (TokenNotFoundException $e) { 45 | throw new AuthorizationException(); 46 | } 47 | $this->service->requestAccessToken($params['oauth_token'], $params['oauth_verifier'], $token->getRequestTokenSecret()); 48 | } 49 | 50 | public function getProfile() 51 | { 52 | $path = 'user/info'; 53 | $result = $this->request($path); 54 | $profile_json = json_decode($result, true); 55 | 56 | $mapping = [ 57 | 'id' => 'name', 58 | 'name' => 'name', 59 | 'username' => 'name', 60 | 'likes' => 'likes' 61 | ]; 62 | 63 | $profile = Profile::create($mapping, $profile_json['response']['user']); 64 | $profile->provider = static::$provider; 65 | $profile->raw_response = $result; 66 | $profile->link = 'https://www.tumblr.com'; 67 | 68 | return $profile; 69 | } 70 | 71 | 72 | public function getBlogs() 73 | { 74 | $path = 'user/info'; 75 | $result = $this->request($path); 76 | $profile_json = json_decode($result, true); 77 | 78 | $mapping = [ 79 | 'id' => 'name', 80 | 'link' => 'url', 81 | 'title' => 'title', 82 | 'name' => 'name', 83 | 'description' => 'description', 84 | 'ask' => 'ask', 85 | 'ask_anon' => 'ask_anon', 86 | ]; 87 | 88 | $blogs = []; 89 | 90 | foreach ($profile_json['response']['user']['blogs'] as $blog) { 91 | $blogs[$blog['name']] = Blog::create($mapping, $blog); 92 | } 93 | 94 | return $blogs; 95 | } 96 | 97 | 98 | public function getPermissions() 99 | { 100 | return null; 101 | } 102 | 103 | public function getStats() 104 | { 105 | $profile = $this->getProfile(); 106 | 107 | return $profile->likes; 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/Connectors/TumblrBlog.php: -------------------------------------------------------------------------------- 1 | meta->status; 23 | 24 | if ($status < 200 || $status > 299) { 25 | $msg = $json_result->meta->msg; 26 | 27 | if ($status == 400) { 28 | $media_error_messages = [ 29 | "Error uploading photo.", 30 | "Nice image, but we don't support that format. Try resaving it as a gif, jpg, or png.", 31 | ]; 32 | 33 | foreach ($media_error_messages as $media_error_message) { 34 | if (strpos($json_result->response->errors[0], $media_error_message) !== false) { 35 | $msg .= ': ' . $media_error_message; 36 | } 37 | } 38 | } 39 | 40 | throw new TumblrPostingException($msg, $status); 41 | } 42 | 43 | return $result; 44 | } 45 | 46 | 47 | public function post(Post $post) 48 | { 49 | $path = 'blog/'.$this->options['base_hostname'].'/post'; 50 | $method = 'POST'; 51 | 52 | $params = []; 53 | if (!empty($post->tags)) { 54 | $params['tags'] = $post->tags; 55 | } 56 | 57 | 58 | if (empty($post->media)) { 59 | $body = '

' . $post->body . '

'; 60 | $body .= '' . $post->url . ''; 61 | 62 | $params['type'] = 'text'; 63 | $params['title'] = $post->title; 64 | $params['body'] = $body; 65 | } else { 66 | $caption = '

' . $post->title . '

'; 67 | $caption .= '

' . $post->body . '

'; 68 | $caption .= '' . $post->url . ''; 69 | 70 | $params['type'] = 'photo'; 71 | $params['caption'] = $caption; 72 | $params['source'] = $post->media[0]; 73 | } 74 | 75 | $result = $this->request($path, $method, $params); 76 | 77 | $response = new Response; 78 | $response->setRawResponse(json_encode($result)); 79 | $result_json = json_decode($result); 80 | $response->setProvider('Tumblr'); 81 | $response->setPostId($result_json->response->id); 82 | 83 | return $response; 84 | } 85 | 86 | public function getBlog() 87 | { 88 | $api_key = $this->config['consumer_key']; 89 | $path = 'blog/'.$this->options['base_hostname'].'/info?api_key='.$api_key; 90 | $result = $this->request($path); 91 | $json_result = json_decode($result, true); 92 | 93 | $mapping = [ 94 | 'id' => 'name', 95 | 'link' => 'url', 96 | 'title' => 'title', 97 | 'name' => 'name', 98 | 'description' => 'description', 99 | 'ask' => 'ask', 100 | 'ask_anon' => 'ask_anon', 101 | 'followers' => 'followers' 102 | ]; 103 | 104 | $blog = Blog::create($mapping, $json_result['response']['blog']); 105 | 106 | return $blog; 107 | } 108 | 109 | 110 | public function getPermissions() 111 | { 112 | return null; 113 | } 114 | 115 | public function getStats() 116 | { 117 | $profile = $this->getBlog(); 118 | 119 | return $profile->followers; 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/Connectors/Twitter.php: -------------------------------------------------------------------------------- 1 | $post->body 68 | ]; 69 | 70 | if (!empty($post->media)) { 71 | $params['media_ids'] = implode(',', $post->media); 72 | } 73 | 74 | $result = $this->request($path, $method, $params); 75 | 76 | $response = new Response; 77 | $response->setRawResponse(json_encode($result)); 78 | $result_json = json_decode($result); 79 | $response->setProvider('Twitter'); 80 | $response->setPostId($result_json->id_str); 81 | 82 | return $response; 83 | } 84 | 85 | 86 | public function postMedia($media) 87 | { 88 | $path = 'https://upload.twitter.com/1.1/media/upload.json'; 89 | $method = 'POST'; 90 | $params = [ 91 | 'media' => $media 92 | ]; 93 | 94 | $result = $this->request($path, $method, $params); 95 | $json_result = json_decode($result, true); 96 | 97 | return $json_result; 98 | } 99 | 100 | 101 | /** 102 | * Twitter needs an extra step for authentication before providing an 103 | * authorization URL. 104 | * 105 | * @author Raúl Santos 106 | */ 107 | public function getAuthorizationUri(array $params = []) 108 | { 109 | $token = $this->service->requestRequestToken(); 110 | $extra = ['oauth_token' => $token->getRequestToken()]; 111 | return parent::getAuthorizationUri($extra); 112 | } 113 | 114 | 115 | /** 116 | * Retrieve the auth token from the provider's response and store it. 117 | */ 118 | public function storeOauthToken($params) 119 | { 120 | try { 121 | $token = $this->service->getStorage()->retrieveAccessToken('Twitter'); 122 | } catch (TokenNotFoundException $e) { 123 | throw new AuthorizationException(); 124 | } 125 | $result = $this->service->requestAccessToken($params['oauth_token'], $params['oauth_verifier'], $token->getRequestTokenSecret()); 126 | 127 | $extra_params = $result->getExtraParams(); 128 | $this->user_id = $extra_params['user_id']; 129 | $this->screen_name = $extra_params['screen_name']; 130 | } 131 | 132 | public function getProfile() 133 | { 134 | $path = '/account/verify_credentials.json?skip_status=1'; 135 | $result = $this->request($path); 136 | $profile_json = json_decode($result, true); 137 | 138 | $mapping = [ 139 | 'id' => 'id_str', 140 | // 'email' => 'email', 141 | 'name' => 'name', 142 | 'first_name' => 'first_name', 143 | 'middle_name' => 'middle_name', 144 | 'last_name' => 'last_name', 145 | 'username' => 'screen_name', 146 | 'link' => 'link' 147 | ]; 148 | 149 | $profile = Profile::create($mapping, $profile_json); 150 | $profile->provider = static::$provider; 151 | $profile->raw_response = $result; 152 | $profile->link = 'https://twitter.com/'.$profile_json['screen_name']; 153 | 154 | return $profile; 155 | } 156 | 157 | public function getPermissions() 158 | { 159 | return null; 160 | } 161 | 162 | public function getStats() 163 | { 164 | $path = '/followers/ids.json?user_id='.$this->id; 165 | $response = $this->request($path); 166 | $response = json_decode($response); 167 | $response = count($response->ids); 168 | return $response; 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /src/Exceptions/AuthorizationException.php: -------------------------------------------------------------------------------- 1 | message = sprintf($this->message, $provider); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/Exceptions/LinkedinForbiddenException.php: -------------------------------------------------------------------------------- 1 | getMessage(), $parent->getCode(), $parent); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/Exceptions/LinkedinPostingException.php: -------------------------------------------------------------------------------- 1 | 'id', 24 | * 'email' => 'email', 25 | * 'name' => 'name', 26 | * 'first_name' => 'first_name', 27 | * 'middle_name' => 'middle_name', 28 | * 'last_name' => 'last_name', 29 | * 'username' => 'username', 30 | * 'link' => 'link' 31 | * ]; 32 | * The keys are the name of the Group object attributes, while the values 33 | * are the key of that attribute in the $attributes array. Like so: 34 | * ['group_object_attribute' => 'key_in_attributes_array'] 35 | * 36 | * @author Raúl Santos 37 | */ 38 | public static function create(array $mapping, array $attributes) 39 | { 40 | $group = new Group; 41 | 42 | foreach ($mapping as $key => $name) { 43 | $group->$key = (isset($attributes[$name])) ? $attributes[$name] : null; 44 | } 45 | 46 | return $group; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/Page.php: -------------------------------------------------------------------------------- 1 | 'id', 26 | * 'email' => 'email', 27 | * 'name' => 'name', 28 | * 'first_name' => 'first_name', 29 | * 'middle_name' => 'middle_name', 30 | * 'last_name' => 'last_name', 31 | * 'username' => 'username', 32 | * 'link' => 'link' 33 | * ]; 34 | * The keys are the name of the Page object attributes, while the values 35 | * are the key of that attribute in the $attributes array. Like so: 36 | * ['page_object_attribute' => 'key_in_attributes_array'] 37 | * 38 | * @author Raúl Santos 39 | */ 40 | public static function create(array $mapping, array $attributes) 41 | { 42 | $page = new Page; 43 | 44 | foreach ($mapping as $key => $name) { 45 | $page->$key = (isset($attributes[$name])) ? $attributes[$name] : null; 46 | } 47 | 48 | return $page; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/Post.php: -------------------------------------------------------------------------------- 1 | 'id', 28 | * 'email' => 'email', 29 | * 'name' => 'name', 30 | * 'first_name' => 'first_name', 31 | * 'middle_name' => 'middle_name', 32 | * 'last_name' => 'last_name', 33 | * 'username' => 'username', 34 | * 'link' => 'link' 35 | * ]; 36 | * 37 | * The keys are the name of the Profile object attributes, while the values 38 | * are the key of that attribute in the $attributes array. Like so: 39 | * ['profile_object_attribute' => 'key_in_attributes_array'] 40 | * 41 | * @param array $mapping 42 | * @param array $attributes 43 | * @return static 44 | */ 45 | public static function create(array $mapping, array $attributes) 46 | { 47 | $profile = new Profile; 48 | 49 | array_walk($mapping, function (&$name, $key) use (&$profile, &$attributes) { 50 | $profile->$key = (isset($attributes[$name])) ? $attributes[$name] : null; 51 | }); 52 | 53 | return $profile; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/Response.php: -------------------------------------------------------------------------------- 1 | raw_response = $raw_response; 16 | return $this; 17 | } 18 | 19 | public function setProvider($provider) 20 | { 21 | $this->provider = $provider; 22 | return $this; 23 | } 24 | 25 | public function setPostId($post_id) 26 | { 27 | $this->post_id = $post_id; 28 | return $this; 29 | } 30 | 31 | public function setPostUrl($post_url) 32 | { 33 | $this->post_url = $post_url; 34 | return $this; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Stats.php: -------------------------------------------------------------------------------- 1 | config = [ 14 | 'callback' => 'blah', 15 | 'providers' => [ 16 | 'Twitter' => [ 17 | 'consumer_key' => 'aaa', 18 | 'consumer_secret' => 'bbb', 19 | 'service' => 'Twitter', 20 | ], 21 | 'Facebook' => [ 22 | 'consumer_key' => 'aaa', 23 | 'consumer_secret' => 'bbb', 24 | 'scopes' => 'email, publish_stream, manage_pages, publish_actions', 25 | 'service' => 'Facebook', 26 | ], 27 | 'Linkedin' => [ 28 | 'consumer_key' => 'aaa', 29 | 'consumer_secret' => 'bbb', 30 | 'scopes' => 'r_fullprofile, r_emailaddress, rw_nus, rw_company_admin, r_network, rw_groups', 31 | 'csrf_token_name' => 'state', 32 | 'service' => 'Linkedin', 33 | ], 34 | 'Google' => [ 35 | 'consumer_key' => 'aaa', 36 | 'consumer_secret' => 'bbb', 37 | 'public_api_key' => 'ccc', // Google-specific 38 | 'service' => 'Google', 39 | ], 40 | ] 41 | ]; 42 | 43 | $this->config['providers']['FacebookPage'] = $this->config['providers']['Facebook']; 44 | $this->config['providers']['LinkedinPage'] = $this->config['providers']['Linkedin']; 45 | $this->config['providers']['LinkedinGroup'] = $this->config['providers']['Linkedin']; 46 | 47 | $this->id = 'foo'; 48 | 49 | $this->mock_storage = m::mock("OAuth\\Common\\Storage\\TokenStorageInterface"); 50 | $this->factory = new ConnectorFactory($this->config); 51 | } 52 | 53 | 54 | public function tearDown() 55 | { 56 | m::close(); 57 | } 58 | 59 | 60 | public function testEmptyArrayConfigThrowsException() 61 | { 62 | $this->setExpectedException('Borfast\\Socializr\\Exceptions\\InvalidConfigurationException'); 63 | 64 | $factory = new ConnectorFactory([]); 65 | } 66 | 67 | 68 | public function testCreateTwitterConnectorReturnsCorrectClass() 69 | { 70 | $connector = $this->factory->createConnector( 71 | 'Twitter', 72 | $this->mock_storage, 73 | [], 74 | $this->id 75 | ); 76 | 77 | $this->assertInstanceOf("\\Borfast\\Socializr\\Connectors\\Twitter", $connector); 78 | } 79 | 80 | 81 | public function testCreateFacebookConnectorReturnsCorrectClass() 82 | { 83 | $connector = $this->factory->createConnector( 84 | 'Facebook', 85 | $this->mock_storage, 86 | [], 87 | $this->id 88 | ); 89 | 90 | $this->assertInstanceOf("\\Borfast\\Socializr\\Connectors\\Facebook", $connector); 91 | } 92 | 93 | 94 | public function testCreateFacebookPageConnectorReturnsCorrectClass() 95 | { 96 | $connector = $this->factory->createConnector( 97 | 'FacebookPage', 98 | $this->mock_storage, 99 | [], 100 | $this->id 101 | ); 102 | 103 | $this->assertInstanceOf("\\Borfast\\Socializr\\Connectors\\FacebookPage", $connector); 104 | } 105 | 106 | 107 | public function testCreateLinkedinConnectorReturnsCorrectClass() 108 | { 109 | $connector = $this->factory->createConnector( 110 | 'Linkedin', 111 | $this->mock_storage, 112 | [], 113 | $this->id 114 | ); 115 | 116 | $this->assertInstanceOf("\\Borfast\\Socializr\\Connectors\\Linkedin", $connector); 117 | } 118 | 119 | 120 | public function testCreateLinkedinPageConnectorReturnsCorrectClass() 121 | { 122 | $connector = $this->factory->createConnector( 123 | 'LinkedinPage', 124 | $this->mock_storage, 125 | [], 126 | $this->id 127 | ); 128 | 129 | $this->assertInstanceOf("\\Borfast\\Socializr\\Connectors\\LinkedinPage", $connector); 130 | } 131 | 132 | 133 | public function testCreateLinkedinGroupConnectorReturnsCorrectClass() 134 | { 135 | $connector = $this->factory->createConnector( 136 | 'LinkedinGroup', 137 | $this->mock_storage, 138 | [], 139 | $this->id 140 | ); 141 | 142 | $this->assertInstanceOf("\\Borfast\\Socializr\\Connectors\\LinkedinGroup", $connector); 143 | } 144 | 145 | 146 | public function testCreateInvalidConnectorThrowsException() 147 | { 148 | $this->setExpectedException("Borfast\\Socializr\\Exceptions\\InvalidProviderException"); 149 | 150 | $connector = $this->factory->createConnector( 151 | 'invalid', 152 | $this->mock_storage, 153 | [], 154 | $this->id 155 | ); 156 | } 157 | } 158 | --------------------------------------------------------------------------------