├── LICENSE ├── SEOstats ├── Common │ ├── SEOstatsException.php │ └── AutoLoader.php ├── Config │ ├── Package.php │ ├── ApiKeys.php │ ├── DefaultSettings.php │ └── Services.php ├── Helper │ ├── ArrayHandle.php │ ├── Url.php │ ├── Json.php │ └── HttpRequest.php ├── bootstrap.php ├── Services │ ├── OpenSiteExplorer.php │ ├── Google.php │ ├── Mozscape.php │ ├── Sistrix.php │ ├── Google │ │ └── Search.php │ ├── Social.php │ ├── SemRush.php │ ├── Alexa.php │ └── 3rdparty │ │ ├── GTB_PageRank.php │ │ └── JSON.php └── SEOstats.php ├── composer.json └── README.md /LICENSE: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BrandonShar/SEOstats/master/LICENSE -------------------------------------------------------------------------------- /SEOstats/Common/SEOstatsException.php: -------------------------------------------------------------------------------- 1 | 9 | * @copyright Copyright (c) 2010 - present Stephan Schmitz 10 | * @license http://eyecatchup.mit-license.org/ MIT License 11 | * @updated 2013/02/03 12 | */ 13 | 14 | class SEOstatsException extends \Exception 15 | { 16 | } 17 | -------------------------------------------------------------------------------- /SEOstats/Config/Package.php: -------------------------------------------------------------------------------- 1 | 9 | * @copyright Copyright (c) 2010 - present Stephan Schmitz 10 | * @license http://eyecatchup.mit-license.org/ MIT License 11 | * @updated 2013/08/17 12 | */ 13 | 14 | /** 15 | * SEOstats package info 16 | * @package SEOstats 17 | */ 18 | interface Package 19 | { 20 | const VERSION_CODE = '2.5.2'; 21 | const LICENSE_TYPE = 'MIT Licence'; 22 | const LICENSE_URL = 'http://eyecatchup.mit-license.org/'; 23 | const AUTHOR_NAME = 'Stephan Schmitz'; 24 | const AUTHOR_MAIL = 'eyecatchup@gmail.com'; 25 | } 26 | -------------------------------------------------------------------------------- /SEOstats/Helper/ArrayHandle.php: -------------------------------------------------------------------------------- 1 | 9 | * @copyright Copyright (c) 2010 - present Stephan Schmitz 10 | * @license http://eyecatchup.mit-license.org/ MIT License 11 | * @updated 2013/02/03 12 | */ 13 | 14 | class ArrayHandle 15 | { 16 | protected $array; 17 | 18 | public function push($element) 19 | { 20 | $this->array[] = $element; 21 | } 22 | 23 | public function setElement($key, $element) 24 | { 25 | $this->array[$key] = $element; 26 | } 27 | 28 | public function count() 29 | { 30 | return count($this->array); 31 | } 32 | 33 | public function toArray() 34 | { 35 | return $this->array; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /SEOstats/bootstrap.php: -------------------------------------------------------------------------------- 1 | 9 | * @copyright Copyright (c) 2010 - present Stephan Schmitz 10 | * @license http://eyecatchup.mit-license.org/ MIT License 11 | * @updated 2013/12/16 12 | */ 13 | 14 | // @link https://github.com/eyecatchup/SEOstats/pull/42#issuecomment-29747931 15 | if (!ini_get('date.timezone') && function_exists('date_default_timezone_set')) { 16 | date_default_timezone_set('UTC'); 17 | } 18 | 19 | // Exit if the PHP version is not equals or greater 5.3.0. 20 | if (version_compare(PHP_VERSION, '5.3', '<')) { 21 | exit('SEOstats requires PHP version 5.3.0 or greater, but yours is ' . PHP_VERSION); 22 | } 23 | 24 | // Disabling Zend Garbage Collection to prevent segfaults with PHP5.4+ 25 | // @link https://bugs.php.net/bug.php?id=53976 26 | if (version_compare(PHP_VERSION, '5.4', '>=') && gc_enabled()) { 27 | gc_disable(); 28 | } 29 | 30 | /* 31 | *--------------------------------------------------------------- 32 | * Register custom PSR-0 Autoloader 33 | *--------------------------------------------------------------- 34 | */ 35 | 36 | require_once realpath(__DIR__ . '/Common/AutoLoader.php'); 37 | 38 | $autoloader = new \SEOstats\Common\AutoLoader(__NAMESPACE__, dirname(__DIR__)); 39 | 40 | $autoloader->register(); 41 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "seostats/seostats", 3 | "type" : "library", 4 | "description" : "SEOstats is a powerful open source PHP library to request a bunch of SEO relevant metrics for any website.", 5 | "keywords" : [ 6 | "SEO", 7 | "SEOStats", 8 | "SEO-Stats", 9 | "Pagerank", 10 | "Backlinks", 11 | "Google", 12 | "Mozscape", 13 | "SEOmoz", 14 | "Sistrix", 15 | "Open Site Explorer", 16 | "SEMRush", 17 | "Alexa" 18 | ], 19 | "homepage" : "http://github.com/eyecatchup/SEOstats", 20 | "license" : "MIT", 21 | "authors" : [{ 22 | "name" : "Stephan Schmitz", 23 | "email" : "eyecatchup@gmail.com", 24 | "homepage" : "https://github.com/eyecatchup", 25 | "role" : "Creator, Developer, Maintainer" 26 | }, { 27 | "name" : "SEOstats Community", 28 | "homepage" : "https://github.com/eyecatchup/seostats/contributors", 29 | "role" : "Contributor, Developer" 30 | } 31 | ], 32 | "require" : { 33 | "php" : ">=5.3", 34 | "ext-curl" : "*", 35 | "ext-json" : "*", 36 | "vlucas/phpdotenv" : "*" 37 | }, 38 | "require-dev" : { 39 | "squizlabs/php_codesniffer" : "~1", 40 | "composer/composer": "1.0.*@dev", 41 | "phpunit/phpunit" : ">=3.7,<4", 42 | "scrutinizer/ocular" : "~1" 43 | }, 44 | "autoload" : { 45 | "psr-0" : { 46 | "SEOstats\\" : "" 47 | }, 48 | "classmap" : [ 49 | "SEOstats/Services/3rdparty" 50 | ] 51 | }, 52 | "extra": { 53 | "branch-alias": { 54 | "dev-master": "2.5-dev" 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /SEOstats/Helper/Url.php: -------------------------------------------------------------------------------- 1 | 9 | * @copyright Copyright (c) 2010 - present Stephan Schmitz 10 | * @license http://eyecatchup.mit-license.org/ MIT License 11 | * @updated 2013/02/03 12 | */ 13 | 14 | class Url 15 | { 16 | public static function parseHost($url) 17 | { 18 | $url = @parse_url('http://' . preg_replace('#^https?://#', '', $url)); 19 | return (isset($url['host']) && !empty($url['host'])) ? $url['host'] : false; 20 | } 21 | 22 | /** 23 | * Validates the initialized object URL syntax. 24 | * 25 | * @access private 26 | * @param string $url String, containing the initialized object URL. 27 | * @return string Returns string, containing the validation result. 28 | */ 29 | public static function isRfc($url) 30 | { 31 | if(isset($url) && 1 < strlen($url)) { 32 | $host = self::parseHost($url); 33 | $scheme = strtolower(parse_url($url, PHP_URL_SCHEME)); 34 | if (false !== $host && ($scheme == 'http' || $scheme == 'https')) { 35 | $pattern = '([A-Za-z][A-Za-z0-9+.-]{1,120}:[A-Za-z0-9/](([A-Za-z0-9$_.+!*,;/?:@&~=-])'; 36 | $pattern .= '|%[A-Fa-f0-9]{2}){1,333}(#([a-zA-Z0-9][a-zA-Z0-9$_.+!*,;/?:@&~=%-]{0,1000}))?)'; 37 | return (bool) preg_match($pattern, $url); 38 | } 39 | } 40 | return false; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /SEOstats/Config/ApiKeys.php: -------------------------------------------------------------------------------- 1 | 9 | * @copyright Copyright (c) 2010 - present Stephan Schmitz 10 | * @license http://eyecatchup.mit-license.org/ MIT License 11 | * @updated 2013/12/11 12 | */ 13 | 14 | /** 15 | * Client API keys 16 | * @package SEOstats 17 | */ 18 | class ApiKeys 19 | { 20 | // To acquire an API key, visit Google's APIs Console here: 21 | // https://code.google.com/apis/console 22 | // In the Services pane, activate the "PageSpeed Insights API" (not the service!). 23 | // Next, go to the API Access pane. The API key is near the bottom of that pane, 24 | // in the section titled "Simple API Access.". 25 | const GOOGLE_SIMPLE_API_ACCESS_KEY = ''; 26 | 27 | // To acquire a Mozscape (f.k.a. SEOmoz) API key, visit: 28 | // https://moz.com/products/api/keys 29 | const MOZSCAPE_ACCESS_ID = ''; 30 | const MOZSCAPE_SECRET_KEY = ''; 31 | 32 | // To acquire a SISTRIX API key, visit: 33 | // http://www.sistrix.de 34 | const SISTRIX_API_ACCESS_KEY = ''; 35 | 36 | public static function getGoogleSimpleApiAccessKey() { 37 | return env('GOOGLE_SIMPLE_API_ACCESS_KEY', self::GOOGLE_SIMPLE_API_ACCESS_KEY); 38 | } 39 | 40 | public static function getMozscapeAccessId() { 41 | return env('MOZSCAPE_ACCESS_ID', self::MOZSCAPE_ACCESS_ID); 42 | } 43 | 44 | public static function getMozscapeSecretKey() 45 | { 46 | return env('MOZSCAPE_SECRET_KEY', self::MOZSCAPE_SECRET_KEY); 47 | } 48 | 49 | public static function getSistrixApiAccessKey() 50 | { 51 | return env('SISTRIX_API_ACCESS_KEY', self::SISTRIX_API_ACCESS_KEY); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /SEOstats/Helper/Json.php: -------------------------------------------------------------------------------- 1 | 9 | * @copyright Copyright (c) 2010 - present Stephan Schmitz 10 | * @license http://eyecatchup.mit-license.org/ MIT License 11 | * @updated 2013/02/03 12 | */ 13 | 14 | class Json 15 | { 16 | /** 17 | * Decodes a JSON string into appropriate variable. 18 | * 19 | * @param string $str JSON-formatted string 20 | * @param boolean $accos When true, returned objects will be converted into associative arrays. 21 | * @return mixed number, boolean, string, array, or object corresponding to given JSON input string. 22 | * Note that decode() always returns strings in ASCII or UTF-8 format! 23 | */ 24 | public static function decode($str, $assoc = false) 25 | { 26 | if (!function_exists('json_decode')) { 27 | $j = self::getJsonService(); 28 | return $j->decode($str); 29 | } 30 | else { 31 | return $assoc ? json_decode($str, true) : json_decode($str); 32 | } 33 | } 34 | 35 | /** 36 | * Encodes an arbitrary variable into JSON format. 37 | * 38 | * @param mixed $var any number, boolean, string, array, or object to be encoded. 39 | * if var is a string, note that encode() always expects it 40 | * to be in ASCII or UTF-8 format! 41 | * @return mixed JSON string representation of input var or an error if a problem occurs 42 | */ 43 | public static function encode($var) 44 | { 45 | if (!function_exists('json_encode')) { 46 | $j = self::getJsonService(); 47 | return $j->encode($var); 48 | } 49 | else { 50 | return json_encode($var); 51 | } 52 | } 53 | 54 | /** 55 | * Return a new object of Services_JSON class. 56 | * Used if native PHP JSON extension is not available. 57 | */ 58 | private static function getJsonService() 59 | { 60 | return new \Services_JSON(); 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /SEOstats/Common/AutoLoader.php: -------------------------------------------------------------------------------- 1 | 9 | * @copyright Copyright (c) 2010 - present Stephan Schmitz 10 | * @license http://eyecatchup.mit-license.org/ MIT License 11 | * @updated 2013/02/03 12 | */ 13 | class AutoLoader 14 | { 15 | /** 16 | * @var string The namespace prefix for this instance. 17 | */ 18 | protected $namespace = ''; 19 | 20 | /** 21 | * @var string The filesystem prefix to use for this instance 22 | */ 23 | protected $path = ''; 24 | 25 | /** 26 | * Build the instance of the autoloader 27 | * 28 | * @param string $namespace The prefixed namespace this instance will load 29 | * @param string $path The filesystem path to the root of the namespace 30 | */ 31 | public function __construct($namespace, $path) 32 | { 33 | $this->namespace = ltrim($namespace, '\\'); 34 | $this->path = rtrim($path, '/\\') . DIRECTORY_SEPARATOR; 35 | } 36 | 37 | /** 38 | * Try to load a class 39 | * 40 | * @param string $class The class name to load 41 | * 42 | * @return boolean If the loading was successful 43 | */ 44 | public function load($className) 45 | { 46 | $class = ltrim($className, '\\'); 47 | 48 | if (strpos($class, $this->namespace) !== 0) { 49 | return false; 50 | } 51 | 52 | $nsparts = explode('\\', $class); 53 | $class = array_pop($nsparts); 54 | $nsparts[] = ''; 55 | $path = $this->path . implode(DIRECTORY_SEPARATOR, $nsparts); 56 | $path .= str_replace('_', DIRECTORY_SEPARATOR, $class) . '.php'; 57 | 58 | if (!is_readable($path)) { 59 | return false; 60 | } 61 | 62 | require $path; 63 | 64 | return class_exists($className,false); 65 | } 66 | 67 | /** 68 | * Register the autoloader to PHP 69 | * 70 | * @return boolean The status of the registration 71 | */ 72 | public function register() 73 | { 74 | return spl_autoload_register(array($this, 'load')); 75 | } 76 | 77 | /** 78 | * Unregister the autoloader to PHP 79 | * 80 | * @return boolean The status of the unregistration 81 | */ 82 | public function unregister() 83 | { 84 | return spl_autoload_unregister(array($this, 'load')); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /SEOstats/Services/OpenSiteExplorer.php: -------------------------------------------------------------------------------- 1 | 9 | * @copyright Copyright (c) 2010 - present Stephan Schmitz 10 | * @license http://eyecatchup.mit-license.org/ MIT License 11 | * @updated 2013/12/08 12 | */ 13 | 14 | use SEOstats\SEOstats as SEOstats; 15 | use SEOstats\Config as Config; 16 | 17 | class OpenSiteExplorer extends SEOstats 18 | { 19 | public static function getPageMetrics($url = false) 20 | { 21 | $url = parent::getUrl($url); 22 | $dataUrl = sprintf(Config\Services::OPENSITEEXPLORER_URL, 'links', '1', urlencode($url)); 23 | 24 | $html = static::_getPage($dataUrl); 25 | $doc = parent::_getDOMDocument($html); 26 | $xpath = parent::_getDOMXPath(@$doc); 27 | 28 | $tooltipNodes = @$xpath->query('//*[@class="tooltip"]'); 29 | $resultNodes = @$xpath->query('//*[@class="has-tooltip"]'); 30 | $resultNodes2 = @$xpath->query('//*[@class="has-tooltip"]/span[1]'); 31 | $unitNodes = @$xpath->query('//*[@class="has-tooltip"]/span[2]'); 32 | 33 | if (0 < $resultNodes->length) { 34 | return (object) array( 35 | 'domainAuthority' => (object) array( 36 | 'result' => intval($resultNodes2->item(0)->nodeValue), 37 | 'unit' => trim(strip_tags($unitNodes->item(0)->nodeValue)), 38 | 'descr' => trim(strip_tags($tooltipNodes->item(0)->nodeValue)), 39 | ), 40 | 'pageAuthority' => (object) array( 41 | 'result' => intval($resultNodes2->item(1)->nodeValue), 42 | 'unit' => trim(strip_tags($unitNodes->item(1)->nodeValue)), 43 | 'descr' => trim(strip_tags($tooltipNodes->item(1)->nodeValue)), 44 | ), 45 | 'justDiscovered' => (object) array( 46 | 'result' => intval(str_replace(',', '', strip_tags($resultNodes->item(2)->nodeValue))), 47 | 'unit' => trim(strip_tags($resultNodes2->item(2)->nodeValue)), 48 | 'descr' => trim(strip_tags($tooltipNodes->item(2)->nodeValue)), 49 | ), 50 | 'linkingRootDomains' => (object) array( 51 | 'result' => intval(str_replace(',', '', $resultNodes->item(3)->nodeValue)), 52 | 'unit' => trim(strip_tags($resultNodes2->item(3)->nodeValue)), 53 | 'descr' => trim(strip_tags($tooltipNodes->item(3)->nodeValue)), 54 | ), 55 | 'totalLinks' => (object) array( 56 | 'result' => intval(str_replace(',', '', $resultNodes->item(4)->nodeValue)), 57 | 'unit' => trim(strip_tags($resultNodes2->item(4)->nodeValue)), 58 | 'descr' => trim(strip_tags($tooltipNodes->item(4)->nodeValue)), 59 | ), 60 | ); 61 | } 62 | else { 63 | return parent::noDataDefaultValue(); 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /SEOstats/Config/DefaultSettings.php: -------------------------------------------------------------------------------- 1 | 9 | * @copyright Copyright (c) 2010 - present Stephan Schmitz 10 | * @license http://eyecatchup.mit-license.org/ MIT License 11 | * @updated 2013/08/17 12 | */ 13 | 14 | /** 15 | * Default client settings 16 | * @package SEOstats 17 | */ 18 | interface DefaultSettings 19 | { 20 | // The default value returned by all SEOstats methods if no result available. 21 | // Can be either of type String, Int, Bool or NULL. 22 | const DEFAULT_RETURN_NO_DATA = 'n.a.'; 23 | 24 | // The default top level domain ending to use to query Google. 25 | const GOOGLE_TLD = 'com'; 26 | 27 | // The HTTP header value for the 'Accept-Language' attribute. 28 | // 29 | // Note: Google search results, doesn't matter which tld you request, vary depending on 30 | // the value sent for the HTTP header attribute 'Accept-Language'! Eg: I am from Germany. 31 | // Even if I use the "ncr" (no country redirect) request parameter, all search results 32 | // that I get in response to a query on google.com will be localized to German, because 33 | // my browser sends an Accept-Language header value of 'de-de,de;q=0.8,en-us;q=0.5,en;q=0.3'. 34 | // On the other side, if I change my browser settings to send a value of 'en-us;q=0.8,en;q=0.3', 35 | // all my searches on google.de (the german Google page) will be localized English. 36 | // Thus, if you want to get the same results that you see when you search Google from 37 | // your browser, you must not only set the @const GOOGLE_TLD to your country specifiy TLD, 38 | // but also set the value below to be the same used by your browser! 39 | const HTTP_HEADER_ACCEPT_LANGUAGE = 'en-us;q=0.8,en;q=0.3'; 40 | 41 | // For curl instances: Whether to allow Google to store cookies, or not. 42 | const ALLOW_GOOGLE_COOKIES = 0; 43 | 44 | // Choose the local SEMRush database to use. 45 | // Valid values are: 46 | // au - Google.com.au (Australia) 47 | // br - Google.com.br (Brazil) 48 | // ca - Google.ca (Canada) 49 | // de - Google.de (Germany) 50 | // es - Google.es (Spain) 51 | // fr - Google.fr (France) 52 | // it - Google.it (Italy) 53 | // ru - Google.ru (Russia) 54 | // uk - Google.co.uk (United Kingdom) 55 | // us - Google.com (United States) 56 | const SEMRUSH_DB = 'us'; 57 | 58 | // Choose the local SISTRIX database to use. 59 | // Valid values are: 60 | // de – Germany 61 | // at – Austria 62 | // ch – Switzerland 63 | // us – USA 64 | // uk – England 65 | // es – Spain 66 | // fr – France 67 | // it – Italy 68 | const SISTRIX_DB = 'de'; 69 | 70 | // Enter proxy to use with curl 71 | // leave empty to disable proxy 72 | // can be overwritten by dynamic configuration 73 | const CURLOPT_PROXY = ''; 74 | 75 | // Enter proxy username and password (seperated by :) if necessary 76 | // leave empty to disable 77 | // can be overwritten by dynamic configuration 78 | const CURLOPT_PROXYUSERPWD = ''; 79 | 80 | // Enter own useragent 81 | // leave empty to use default 82 | // can be overwritten by dynamic configuration 83 | const UA = ''; 84 | } 85 | -------------------------------------------------------------------------------- /SEOstats/Config/Services.php: -------------------------------------------------------------------------------- 1 | 9 | * @copyright Copyright (c) 2010 - present Stephan Schmitz 10 | * @license http://eyecatchup.mit-license.org/ MIT License 11 | * @updated 2013/12/16 12 | */ 13 | 14 | /** 15 | * SEOstats provider list and service URLs 16 | * @package SEOstats 17 | */ 18 | interface Services 19 | { 20 | const PROVIDER = '["alexa","google","ose","semrush","mozscape","sistrix","social"]'; 21 | 22 | // Alexa public report URLs. 23 | const ALEXA_SITEINFO_URL = 'http://www.alexa.com/siteinfo/%s'; 24 | const ALEXA_GRAPH_URL = 'http://traffic.alexa.com/graph?&o=f&c=1&y=%s&b=ffffff&n=666666&w=%s&h=%s&r=%sm&u=%s'; 25 | 26 | // Sistrix Visibility Index public report URL. 27 | // @link http://www.sistrix.com/blog/870-sistrix-visibilityindex.html 28 | const SISTRIX_VI_URL = 'http://www.sichtbarkeitsindex.de/%s'; 29 | const SISTRIX_API_VI_URL = 'http://api.sistrix.net/domain.sichtbarkeitsindex?api_key=%s&domain=%s&country=%s&format=json'; 30 | const SISTRIX_API_CREDITS_URL = 'http://api.sistrix.net/credits?api_key=%s&format=json'; 31 | 32 | // SEMrush API Endpoints. 33 | const SEMRUSH_BE_URL = 'http://%s.backend.semrush.com/?action=report&type=%s&domain=%s'; 34 | const SEMRUSH_GRAPH_URL = 'http://semrush.com/archive/graphs.php?domain=%s&db=%s&type=%s&w=%s&h=%s&lc=%s&dc=%s&l=%s'; 35 | const SEMRUSH_WIDGET_URL = 'http://widget.semrush.com/widget.php?action=report&type=%s&db=%s&domain=%s'; 36 | 37 | // Mozscape (f.k.a. Seomoz) Link metrics API Endpoint. 38 | const MOZSCAPE_API_URL = 'http://lsapi.seomoz.com/linkscape/url-metrics/%s?Cols=%s&AccessID=%s&Expires=%s&Signature=%s'; 39 | 40 | // Google Websearch API Endpoint. 41 | const GOOGLE_APISEARCH_URL = 'http://ajax.googleapis.com/ajax/services/search/web?v=1.0&rsz=%s&q=%s'; 42 | 43 | // Google Pagespeed Insights API Endpoint. 44 | const GOOGLE_PAGESPEED_URL = 'https://www.googleapis.com/pagespeedonline/v2/runPagespeed?url=%s&strategy=%s&key=%s'; 45 | 46 | // Google +1 Fastbutton URL. 47 | const GOOGLE_PLUSONE_URL = 'https://plusone.google.com/u/0/_/+1/fastbutton?count=true&url=%s'; 48 | 49 | // Open Site Explorer's public report URL. 50 | const OPENSITEEXPLORER_URL = 'http://www.opensiteexplorer.org/%s?group=0&page=%s&site=%s&sort='; 51 | 52 | // Facebook FQL API Endpoint. 53 | const FB_LINKSTATS_URL = 'https://api.facebook.com/method/fql.query?query=%s&format=json'; 54 | 55 | // Twitter URL tweet count API Endpoint (Use of this Endpoint is actually not allowed (see link)!). 56 | // @link https://dev.twitter.com/discussions/5653#comment-11514 57 | const TWEETCOUNT_URL = 'http://cdn.api.twitter.com/1/urls/count.json?url=%s'; 58 | 59 | // Delicious API Endpoint. 60 | const DELICIOUS_INFO_URL = 'http://feeds.delicious.com/v2/json/urlinfo/data?url=%s'; 61 | 62 | // Digg API Endpoint. 63 | // @link http://widgets.digg.com/buttons.js 64 | const DIGG_INFO_URL = 'http://widgets.digg.com/buttons/count?url=%s&cb=_'; 65 | 66 | // LinkedIn API Endpoint. 67 | // Replaces deprecated share count Url "http://www.linkedin.com/cws/share-count?url=%s". 68 | // @link http://developer.linkedin.com/forum/discrepancies-between-share-counts 69 | const LINKEDIN_INFO_URL = 'http://www.linkedin.com/countserv/count/share?url=%s&callback=_'; 70 | 71 | // Pinterest API Endpoint. 72 | const PINTEREST_INFO_URL = 'http://api.pinterest.com/v1/urls/count.json?url=%s&callback=_'; 73 | 74 | // StumbleUpon API Endpoint. 75 | const STUMBLEUPON_INFO_URL = 'http://www.stumbleupon.com/services/1.01/badge.getinfo?url=%s'; 76 | 77 | // Url to get share count via VKontakte from 78 | const VKONTAKTE_INFO_URL = 'http://vk.com/share.php?act=count&index=1&url=%s'; 79 | 80 | // Url to get share count via Xing from 81 | const XING_SHAREBUTTON_URL = 'https://www.xing-share.com/app/share?op=get_share_button;counter=top;url=%s'; 82 | } 83 | -------------------------------------------------------------------------------- /SEOstats/Services/Google.php: -------------------------------------------------------------------------------- 1 | 9 | * @copyright Copyright (c) 2010 - present Stephan Schmitz 10 | * @license http://eyecatchup.mit-license.org/ MIT License 11 | * @updated 2013/12/17 12 | */ 13 | 14 | use SEOstats\Common\SEOstatsException as E; 15 | use SEOstats\SEOstats as SEOstats; 16 | use SEOstats\Config as Config; 17 | use SEOstats\Helper as Helper; 18 | 19 | class Google extends SEOstats 20 | { 21 | /** 22 | * Gets the Google Pagerank 23 | * 24 | * @param string $url String, containing the query URL. 25 | * @return integer Returns the Google PageRank. 26 | */ 27 | public static function getPageRank($url = false) 28 | { 29 | // Composer autoloads classes out of the SEOstats namespace. 30 | // The custom autolader, however, does not. So we need to include it first. 31 | if(!class_exists('\GTB_PageRank')) { 32 | require_once realpath(__DIR__ . '/3rdparty/GTB_PageRank.php'); 33 | } 34 | 35 | $gtb = new \GTB_PageRank(parent::getUrl($url)); 36 | $result = $gtb->getPageRank(); 37 | 38 | return $result != "" ? $result : static::noDataDefaultValue(); 39 | } 40 | 41 | /** 42 | * Returns the total amount of results for a Google 'site:'-search for the object URL. 43 | * 44 | * @param string $url String, containing the query URL. 45 | * @return integer Returns the total site-search result count. 46 | */ 47 | public static function getSiteindexTotal($url = false) 48 | { 49 | $url = parent::getUrl($url); 50 | $query = urlencode("site:{$url}"); 51 | 52 | return self::getSearchResultsTotal($query); 53 | } 54 | 55 | /** 56 | * Returns the total amount of results for a Google 'link:'-search for the object URL. 57 | * 58 | * @param string $url String, containing the query URL. 59 | * @return integer Returns the total link-search result count. 60 | */ 61 | public static function getBacklinksTotal($url = false) 62 | { 63 | $url = parent::getUrl($url); 64 | $query = urlencode("link:{$url}"); 65 | 66 | return self::getSearchResultsTotal($query); 67 | } 68 | 69 | /** 70 | * Returns total amount of results for any Google search, 71 | * requesting the deprecated Websearch API. 72 | * 73 | * @param string $url String, containing the query URL. 74 | * @return integer Returns the total search result count. 75 | */ 76 | public static function getSearchResultsTotal($url = false) 77 | { 78 | $url = parent::getUrl($url); 79 | $url = sprintf(Config\Services::GOOGLE_APISEARCH_URL, 1, $url); 80 | 81 | $ret = static::_getPage($url); 82 | 83 | $obj = Helper\Json::decode($ret); 84 | return !isset($obj->responseData->cursor->estimatedResultCount) 85 | ? parent::noDataDefaultValue() 86 | : intval($obj->responseData->cursor->estimatedResultCount); 87 | } 88 | 89 | public static function getPagespeedAnalysis($url = false, $strategy = 'desktop') 90 | { 91 | if ('' == Config\ApiKeys::getGoogleSimpleApiAccessKey()) { 92 | throw new E('In order to use the PageSpeed API, you must obtain 93 | and set an API key first (see SEOstats\Config\ApiKeys.php).'); 94 | exit(0); 95 | } 96 | 97 | $url = parent::getUrl($url); 98 | $url = sprintf(Config\Services::GOOGLE_PAGESPEED_URL, 99 | $url, $strategy, Config\ApiKeys::getGoogleSimpleApiAccessKey()); 100 | 101 | $ret = static::_getPage($url); 102 | 103 | return Helper\Json::decode($ret); 104 | } 105 | 106 | public static function getPagespeedScore($url = false, $strategy = 'desktop') 107 | { 108 | $url = parent::getUrl($url); 109 | $ret = self::getPagespeedAnalysis($url, $strategy); 110 | 111 | // Check if $ret->score exists for backwards compatibility with v1. 112 | if (!isset($ret->score)) { 113 | return !isset($ret->ruleGroups->SPEED->score) || !$ret->ruleGroups->SPEED->score ? parent::noDataDefaultValue() : 114 | intval($ret->ruleGroups->SPEED->score); 115 | } 116 | else { 117 | return $ret->score; 118 | } 119 | } 120 | 121 | /** 122 | * Returns array, containing detailed results for any Google search. 123 | * 124 | * @param string $query String, containing the search query. 125 | * @param string $tld String, containing the desired Google top level domain. 126 | * @return array Returns array, containing the keys 'URL', 'Title' and 'Description'. 127 | */ 128 | public static function getSerps($query, $maxResults=100, $domain=false) 129 | { 130 | return Google\Search::getSerps($query, $maxResults, $domain); 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /SEOstats/Services/Mozscape.php: -------------------------------------------------------------------------------- 1 | $blocksize) { 130 | $key = pack('H*', $hashfunc($key)); 131 | } 132 | 133 | $key = str_pad($key, $blocksize, chr(0x00)); 134 | $ipad = str_repeat(chr(0x36), $blocksize); 135 | $opad = str_repeat(chr(0x5c), $blocksize); 136 | $hmac = pack('H*', $hashfunc(($key^$opad) . 137 | pack('H*', $hashfunc(($key^$ipad) . $data)))); 138 | return $hmac; 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /SEOstats/Services/Sistrix.php: -------------------------------------------------------------------------------- 1 | 9 | * @copyright Copyright (c) 2010 - present Stephan Schmitz 10 | * @license http://eyecatchup.mit-license.org/ MIT License 11 | * @updated 2013/08/14 12 | */ 13 | 14 | use SEOstats\Common\SEOstatsException as E; 15 | use SEOstats\SEOstats as SEOstats; 16 | use SEOstats\Config as Config; 17 | use SEOstats\Helper as Helper; 18 | 19 | class Sistrix extends SEOstats 20 | { 21 | public static function getDBs() 22 | { 23 | return array( 24 | 'de', // de – Germany 25 | 'at', // at – Austria 26 | 'ch', // ch – Switzerland 27 | 'us', // us – USA 28 | 'uk', // uk – England 29 | 'es', // es – Spain 30 | 'fr', // fr – France 31 | 'it', // it – Italy 32 | ); 33 | } 34 | 35 | /** 36 | * Returns the Sistrix visibility index 37 | * 38 | * @access public 39 | * @param url string The URL to check. 40 | * @return integer Returns the Sistrix visibility index. 41 | * @link http://www.sistrix.com/blog/870-sistrix-visibilityindex.html 42 | */ 43 | public static function getVisibilityIndex($url = false) 44 | { 45 | $url = parent::getUrl($url); 46 | $domain = Helper\Url::parseHost($url); 47 | $dataUrl = sprintf(Config\Services::SISTRIX_VI_URL, urlencode($domain)); 48 | 49 | $html = static::_getPage($dataUrl); 50 | @preg_match_all('#

(.*?)<\/h3>#si', $html, $matches); 51 | 52 | return isset($matches[1][0]) ? $matches[1][0] : parent::noDataDefaultValue(); 53 | } 54 | 55 | /** 56 | * Returns the Sistrix visibility index by using the SISTRIX API 57 | * 58 | * @access public 59 | * @param url string The URL to check. 60 | * @return integer Returns the Sistrix visibility index. 61 | * @link http://www.sistrix.com/blog/870-sistrix-visibilityindex.html 62 | */ 63 | public static function getVisibilityIndexByApi($url = false, $db = false) 64 | { 65 | self::guardApiKey(); 66 | self::guardApiCredits(); 67 | 68 | $url = parent::getUrl($url); 69 | $domain = static::getDomainFromUrl($url); 70 | $database = static::getValidDatabase($db); 71 | 72 | $dataUrl = sprintf(Config\Services::SISTRIX_API_VI_URL, Config\ApiKeys::getSistrixApiAccessKey(), urlencode($domain), $database); 73 | 74 | $json = static::_getPage($dataUrl); 75 | 76 | if(empty($json)) { 77 | return parent::noDataDefaultValue(); 78 | } 79 | 80 | $json_decoded = (Helper\Json::decode($json, true)); 81 | if (!isset($json_decoded['answer'][0]['sichtbarkeitsindex'][0]['value'])) { 82 | return parent::noDataDefaultValue(); 83 | } 84 | return $json_decoded['answer'][0]['sichtbarkeitsindex'][0]['value']; 85 | } 86 | 87 | public static function getApiCredits() 88 | { 89 | self::guardApiKey(); 90 | 91 | $dataUrl = sprintf(Config\Services::SISTRIX_API_CREDITS_URL, Config\ApiKeys::getSistrixApiAccessKey()); 92 | $json = static::_getPage($dataUrl); 93 | 94 | if(empty($json)) { 95 | return parent::noDataDefaultValue(); 96 | } 97 | 98 | $json_decoded = (Helper\Json::decode($json, true)); 99 | if (!isset($json_decoded['answer'][0]['credits'][0]['value'])) { 100 | return parent::noDataDefaultValue(); 101 | } 102 | return $json_decoded['answer'][0]['credits'][0]['value']; 103 | } 104 | 105 | public static function checkApiCredits() 106 | { 107 | return static::getApiCredits() > 0; 108 | } 109 | 110 | protected static function guardApiKey() 111 | { 112 | if(!static::hasApiKey()) { 113 | self::exc('In order to use the SISTRIX API, you must obtain and set an ' . 114 | 'API key first (see SEOstats\Config\ApiKeys.php).' . PHP_EOL); 115 | } 116 | } 117 | 118 | protected static function hasApiKey() 119 | { 120 | if ('' == Config\ApiKeys::getSistrixApiAccessKey()) { 121 | return false; 122 | } 123 | 124 | return true; 125 | } 126 | 127 | protected static function guardApiCredits() 128 | { 129 | if(!static::checkApiCredits()) { 130 | self::exc('Not enough API credits.'.PHP_EOL); 131 | } 132 | } 133 | 134 | private static function checkDatabase($db) 135 | { 136 | return !in_array($db, self::getDBs()) ? false : $db; 137 | } 138 | 139 | protected static function getDomainFromUrl($url) 140 | { 141 | $url = parent::getUrl($url); 142 | $domain = Helper\Url::parseHost($url); 143 | static::guardDomainIsValid($domain); 144 | 145 | return $domain; 146 | } 147 | 148 | protected static function getValidDatabase($db) 149 | { 150 | $db = ($db == false) ? Config\DefaultSettings::SISTRIX_DB : $db; 151 | 152 | $database = self::checkDatabase($db); 153 | static::guardDatabaseIsValid($database); 154 | 155 | return $database; 156 | } 157 | 158 | protected static function guardDatabaseIsValid($database) 159 | { 160 | if (false === $database) { 161 | self::exc('db'); 162 | } 163 | } 164 | 165 | protected static function guardDomainIsValid($domain) 166 | { 167 | if (false == $domain) { 168 | self::exc('Invalid domain name.'); 169 | } 170 | } 171 | 172 | private static function exc($err) 173 | { 174 | $e = ($err == 'db') 175 | ? "Invalid database. Choose one of: " . 176 | substr( implode(", ", self::getDBs()), 0, -2) 177 | : $err; 178 | throw new E($e); 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /SEOstats/Helper/HttpRequest.php: -------------------------------------------------------------------------------- 1 | 11 | * @copyright Copyright (c) 2010 - present Stephan Schmitz 12 | * @license http://eyecatchup.mit-license.org/ MIT License 13 | * @updated 2016/03/17 14 | */ 15 | 16 | class HttpRequest 17 | { 18 | /** 19 | * HTTP GET/POST request with curl. 20 | * @access public 21 | * @param String $url The Request URL 22 | * @param Array $postData Optional: POST data array to be send. 23 | * @return Mixed On success, returns the response string. 24 | * Else, the the HTTP status code received 25 | * in reponse to the request. 26 | */ 27 | public static function sendRequest($url, $postData = false, $postJson = false) 28 | { 29 | $ua = self::getUserAgent(); 30 | $curlopt_proxy = self::getProxy(); 31 | $curlopt_proxyuserpwd = self::getProxyUserPwd(); 32 | 33 | $ch = curl_init($url); 34 | curl_setopt_array($ch, array( 35 | CURLOPT_USERAGENT => $ua, 36 | CURLOPT_RETURNTRANSFER => 1, 37 | CURLOPT_CONNECTTIMEOUT => 30, 38 | CURLOPT_FOLLOWLOCATION => 1, 39 | CURLOPT_MAXREDIRS => 2, 40 | CURLOPT_SSL_VERIFYPEER => 0, 41 | )); 42 | if($curlopt_proxy) { 43 | curl_setopt($ch, CURLOPT_PROXY, $curlopt_proxy); 44 | } 45 | if($curlopt_proxyuserpwd) { 46 | curl_setopt($ch, CURLOPT_PROXYUSERPWD, $curlopt_proxyuserpwd); 47 | } 48 | 49 | if (false !== $postData) { 50 | if (false !== $postJson) { 51 | curl_setopt($ch, CURLOPT_HTTPHEADER, 52 | array('Content-type: application/json')); 53 | $data = json_encode($postData); 54 | } else { 55 | $data = http_build_query($postData); 56 | } 57 | curl_setopt($ch, CURLOPT_POST, 1); 58 | curl_setopt($ch, CURLOPT_POSTFIELDS, $data); 59 | } 60 | 61 | $response = curl_exec($ch); 62 | $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); 63 | curl_close($ch); 64 | 65 | return (200 == (int)$httpCode) ? $response : false; 66 | } 67 | 68 | /** 69 | * HTTP HEAD request with curl. 70 | * 71 | * @access private 72 | * @param String $a The request URL 73 | * @return Integer Returns the HTTP status code received in 74 | * response to a GET request of the input URL. 75 | */ 76 | public static function getHttpCode($url) 77 | { 78 | $ua = self::getUserAgent(); 79 | $curlopt_proxy = self::getProxy(); 80 | $curlopt_proxyuserpwd = self::getProxyUserPwd(); 81 | 82 | $ch = curl_init($url); 83 | curl_setopt_array($ch, array( 84 | CURLOPT_USERAGENT => $ua, 85 | CURLOPT_RETURNTRANSFER => 1, 86 | CURLOPT_CONNECTTIMEOUT => 10, 87 | CURLOPT_FOLLOWLOCATION => 1, 88 | CURLOPT_MAXREDIRS => 2, 89 | CURLOPT_SSL_VERIFYPEER => 0, 90 | CURLOPT_NOBODY => 1, 91 | )); 92 | if($curlopt_proxy) { 93 | curl_setopt($ch, CURLOPT_PROXY, $curlopt_proxy); 94 | } 95 | if($curlopt_proxyuserpwd) { 96 | curl_setopt($ch, CURLOPT_PROXYUSERPWD, $curlopt_proxyuserpwd); 97 | } 98 | 99 | curl_exec($ch); 100 | $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); 101 | curl_close($ch); 102 | 103 | return (int)$httpCode; 104 | } 105 | 106 | public function getFile($url, $file) 107 | { 108 | $ua = self::getUserAgent(); 109 | $curlopt_proxy = self::getProxy(); 110 | $curlopt_proxyuserpwd = self::getProxyUserPwd(); 111 | 112 | $fp = fopen("$file", 'w'); 113 | 114 | $ch = curl_init($url); 115 | curl_setopt_array($ch, array( 116 | CURLOPT_USERAGENT => $ua, 117 | CURLOPT_RETURNTRANSFER => 1, 118 | CURLOPT_CONNECTTIMEOUT => 30, 119 | CURLOPT_FOLLOWLOCATION => 1, 120 | CURLOPT_MAXREDIRS => 2, 121 | CURLOPT_SSL_VERIFYPEER => 0, 122 | CURLOPT_FILE => $fp, 123 | )); 124 | if($curlopt_proxy) { 125 | curl_setopt($ch, CURLOPT_PROXY, $curlopt_proxy); 126 | } 127 | if($curlopt_proxyuserpwd) { 128 | curl_setopt($ch, CURLOPT_PROXYUSERPWD, $curlopt_proxyuserpwd); 129 | } 130 | 131 | curl_exec($ch); 132 | curl_close($ch); 133 | fclose($fp); 134 | 135 | clearstatcache(); 136 | return (bool)(false !== stat($file)); 137 | } 138 | 139 | public static function getUserAgent() { 140 | $ua = sprintf('SEOstats %s https://github.com/eyecatchup/SEOstats', \SEOstats\SEOstats::BUILD_NO); 141 | if(\SEOstats\Config\DefaultSettings::UA !== '') { 142 | $ua = \SEOstats\Config\DefaultSettings::UA; 143 | } 144 | if(\SEOstats\SEOstats::getUserAgent()) { 145 | $ua = \SEOstats\SEOstats::getUserAgent(); 146 | } 147 | return $ua; 148 | } 149 | 150 | public static function getProxy() { 151 | $curlopt_proxy = false; 152 | if(\SEOstats\Config\DefaultSettings::CURLOPT_PROXY !== '') { 153 | $curlopt_proxy = \SEOstats\Config\DefaultSettings::CURLOPT_PROXY; 154 | } 155 | if(\SEOstats\SEOstats::getCurloptProxy()) { 156 | $curlopt_proxy = \SEOstats\SEOstats::getCurloptProxy(); 157 | } 158 | return $curlopt_proxy; 159 | } 160 | 161 | public static function getProxyUserPwd() { 162 | $curlopt_proxyuserpwd = false; 163 | if(\SEOstats\Config\DefaultSettings::CURLOPT_PROXYUSERPWD !== '') { 164 | $curlopt_proxyuserpwd = \SEOstats\Config\DefaultSettings::CURLOPT_PROXYUSERPWD; 165 | } 166 | if(\SEOstats\SEOstats::getCurloptProxyuserpwd()) { 167 | $curlopt_proxyuserpwd = \SEOstats\SEOstats::getCurloptProxyuserpwd(); 168 | } 169 | return $curlopt_proxyuserpwd; 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /SEOstats/Services/Google/Search.php: -------------------------------------------------------------------------------- 1 | 9 | * @copyright Copyright (c) 2010 - present Stephan Schmitz 10 | * @license http://eyecatchup.mit-license.org/ MIT License 11 | * @updated 2013/12/17 12 | */ 13 | 14 | use SEOstats\Common\SEOstatsException as E; 15 | use SEOstats\SEOstats as SEOstats; 16 | use SEOstats\Config as Config; 17 | use SEOstats\Helper as Helper; 18 | 19 | class Search extends SEOstats 20 | { 21 | 22 | /** 23 | * Returns array, containing detailed results for any Google search. 24 | * 25 | * @param string $query String, containing the search query. 26 | * @param string $tld String, containing the desired Google top level domain. 27 | * @return array Returns array, containing the keys 'URL', 'Title' and 'Description'. 28 | */ 29 | public static function getSerps($query, $maxResults=100, $domain=false) 30 | { 31 | $q = rawurlencode($query); 32 | $maxPage = ceil(($maxResults/10)-1); 33 | $result = new Helper\ArrayHandle (); 34 | $pages = 1; 35 | $delay = 0; 36 | 37 | $domainRexExp = static::getDomainFilter($domain); 38 | 39 | for ($start=0; $start<$pages; $start++) { 40 | 41 | $haveNextPage = static::makeRequest ($start, $q, $result, $domainRexExp); 42 | if (!$haveNextPage) { 43 | $pages -= 1; 44 | } else { 45 | $pages += 1; 46 | $delay += 200000; 47 | usleep($delay); 48 | } 49 | 50 | if ($start == $maxPage) { 51 | $pages -= 1; 52 | } 53 | } // for ($start=0; $start<$pages; $start++) 54 | 55 | return $result->toArray(); 56 | } 57 | 58 | protected static function makeRequest ($start, $query, $result, $domainRexExp) 59 | { 60 | $ref = static::getReference($start, $query); 61 | $nextSerp = static::getNextSerp($start, $query); 62 | 63 | $curledSerp = utf8_decode( static::gCurl($nextSerp, $ref) ); 64 | 65 | static::guardNoCaptcha($curledSerp); 66 | 67 | $matches = array(); 68 | preg_match_all('#

(.*?)

#', $curledSerp, $matches); 69 | 70 | if (empty($matches[1])) { 71 | // No [@id="rso"]/li/h3 on currect page 72 | return false; 73 | } 74 | 75 | static::parseResults($matches, $domainRexExp, $start * 10, $result); 76 | 77 | if ( preg_match('#id="?pnnext"?#', $curledSerp) ) { 78 | // Found 'Next'-link on currect page 79 | return true; 80 | } 81 | 82 | // No 'Next'-link on currect page 83 | return false; 84 | } 85 | 86 | protected static function getReference ($start, $query) 87 | { 88 | return 0 == $start 89 | ? 'ncr' 90 | : sprintf('search?q=%s&hl=en&prmd=imvns&start=%s0&sa=N', $query, $start); 91 | } 92 | 93 | protected static function getDomainFilter ($domain) 94 | { 95 | return $domain 96 | ? "#^(https?://)?[^/]*{$domain}#i" 97 | : false; 98 | } 99 | 100 | protected static function getNextSerp ($start, $query) 101 | { 102 | return 0 == $start 103 | ? sprintf('search?q=%s&filter=0', $query) 104 | : sprintf('search?q=%s&filter=0&start=%s0', $query, $start); 105 | } 106 | 107 | protected static function guardNoCaptcha ($response) 108 | { 109 | if (preg_match("#answer[=|/]86640#i", $response)) { 110 | print('Please read: https://support.google.com/websearch/answer/86640'); 111 | exit(); 112 | } 113 | } 114 | 115 | protected static function parseResults ($matches, $domainRexExp, $start, $result) 116 | { 117 | $c = 0; 118 | 119 | foreach ($matches[1] as $link) { 120 | $match = static::parseLink($link); 121 | 122 | $c++; 123 | $resCnt = $start + $c; 124 | if (! $domainRexExp) { 125 | $result->setElement($resCnt, array( 126 | 'url' => $match[1], 127 | 'headline' => trim(strip_tags($match[2])) 128 | )); 129 | } elseif (preg_match($domainRexExp, $match[1])) { 130 | $result->push(array( 131 | 'position' => $resCnt, 132 | 'url' => $match[1], 133 | 'headline' => trim(strip_tags($match[2])) 134 | )); 135 | } 136 | } // foreach ($matches[1] as $link) 137 | } 138 | 139 | protected static function parseLink($link) 140 | { 141 | $isValidLink = preg_match('#]*href=[\'"]?([^\'" ]+)[\'"]?[^>]*>(.*?)#', $link, $match); 142 | 143 | // is valid and not webmaster link 144 | return ( !$isValidLink || self::isAGoogleWebmasterLink($match[1]) ) 145 | ? false 146 | : $match; 147 | } 148 | 149 | protected static function isAGoogleWebmasterLink($url) 150 | { 151 | return preg_match('#^https?://www.google.com/(?:intl/.+/)?webmasters#', $url); 152 | } 153 | 154 | protected static function gCurl($path, $ref, $useCookie = Config\DefaultSettings::ALLOW_GOOGLE_COOKIES) 155 | { 156 | $url = sprintf('https://www.google.%s/', Config\DefaultSettings::GOOGLE_TLD); 157 | $referer = $ref == '' ? $url : $ref; 158 | $url .= $path; 159 | 160 | $ua = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.153 Safari/537.36"; 161 | if (isset($_SERVER["HTTP_USER_AGENT"]) && 0 < strlen($_SERVER["HTTP_USER_AGENT"])) { 162 | $ua = $_SERVER["HTTP_USER_AGENT"]; 163 | } 164 | 165 | $header = array( 166 | 'Host: www.google.' . Config\DefaultSettings::GOOGLE_TLD, 167 | 'Connection: keep-alive', 168 | 'Cache-Control: max-age=0', 169 | 'User-Agent: ' . $ua, 170 | 'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', 171 | 'Referer: ' . $referer, 172 | 'Accept-Language: ' . Config\DefaultSettings::HTTP_HEADER_ACCEPT_LANGUAGE, 173 | 'Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7' 174 | ); 175 | 176 | $ch = curl_init($url); 177 | curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0); 178 | curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1); 179 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); 180 | curl_setopt($ch, CURLOPT_HTTPHEADER, $header); 181 | curl_setopt($ch, CURLOPT_USERAGENT, $ua); 182 | if ($useCookie == 1) { 183 | curl_setopt($ch, CURLOPT_COOKIEJAR, __DIR__ . '/cookie.txt'); 184 | curl_setopt($ch, CURLOPT_COOKIEFILE, __DIR__ . '/cookie.txt'); 185 | } 186 | curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 30); 187 | $result = curl_exec($ch); 188 | $info = curl_getinfo($ch); 189 | curl_close($ch); 190 | return ($info['http_code']!=200) ? false : $result; 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /SEOstats/SEOstats.php: -------------------------------------------------------------------------------- 1 | 18 | * @copyright Copyright (c) 2010 - present Stephan Schmitz 19 | * @license http://eyecatchup.mit-license.org 20 | * @version CVS: $Id: SEOstats.php, v2.5.2 Rev 31 2013/08/14 13:57:17 ssc Exp $ 21 | * @link https://github.com/eyecatchup/SEOstats/ 22 | * ================================================================================ 23 | * LICENSE: Permission is hereby granted, free of charge, to any person obtaining 24 | * a copy of this software and associated documentation files (the "Software'), 25 | * to deal in the Software without restriction, including without limitation the 26 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 27 | * copies of the Software, and to permit persons to whom the Software is furnished 28 | * to do so, subject to the following conditions: 29 | * 30 | * The above copyright notice and this permission notice shall be included in all 31 | * copies or substantial portions of the Software. 32 | * 33 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 34 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 35 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 36 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY 37 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 38 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 39 | * ================================================================================ 40 | */ 41 | 42 | /** 43 | * Check required PHP settings. 44 | */ 45 | if (!function_exists('curl_init')) { 46 | throw new E('SEOstats requires the PHP CURL extension.'); 47 | exit(); 48 | } 49 | 50 | if (1 == ini_get('safe_mode') || 'on' === strtolower(ini_get('safe_mode'))) { 51 | throw new E('Because some SEOstats functions require the CURLOPT_FOLLOWLOCATION flag, ' . 52 | 'you must not run PHP in safe mode! (This flag can not be set in safe mode.)'); 53 | exit(); 54 | } 55 | 56 | /** 57 | * Starting point for the SEOstats library. Example Usage: 58 | * 59 | * 60 | * ... 61 | * $url = 'http://www.domain.tld'; 62 | * 63 | * // Get the Google Toolbar PageRank value. 64 | * $result = \SEOstats\Services\Google::getPageRank($url); 65 | * 66 | * // Get the first 100 results for a Google search for 'query string'. 67 | * $result = \SEOstats\Services\Google::getSerps('query string'); 68 | * 69 | * // Get the first 500 results for a Google search for 'query string'. 70 | * $result = \SEOstats\Services\Google::getSerps('query string', 500); 71 | * 72 | * // Check the first 500 results for a Google search for 'query string' for 73 | * // occurrences of the given domain name and return an array of matching 74 | * // URL's and their position within the serps. 75 | * $result = \SEOstats\Services\Google::getSerps('query string', 500, $url); 76 | * ... 77 | * 78 | * 79 | */ 80 | class SEOstats 81 | { 82 | const BUILD_NO = Config\Package::VERSION_CODE; 83 | 84 | protected static $_url, 85 | $_host, 86 | $_lastHtml, 87 | $_lastLoadedUrl, 88 | $_curlopt_proxy, 89 | $_curlopt_proxyuserpwd, 90 | $_ua 91 | = false; 92 | 93 | public function __construct($url = false) 94 | { 95 | if (false !== $url) { 96 | self::setUrl($url); 97 | } 98 | } 99 | 100 | public function Alexa() 101 | { 102 | return new Service\Alexa; 103 | } 104 | 105 | public function Google() 106 | { 107 | return new Service\Google; 108 | } 109 | 110 | public function Mozscape() 111 | { 112 | return new Service\Mozscape; 113 | } 114 | 115 | public function OpenSiteExplorer() 116 | { 117 | return new Service\OpenSiteExplorer; 118 | } 119 | 120 | public function SEMRush() 121 | { 122 | return new Service\SemRush; 123 | } 124 | 125 | public function Sistrix() 126 | { 127 | return new Service\Sistrix; 128 | } 129 | 130 | public function Social() 131 | { 132 | return new Service\Social; 133 | } 134 | 135 | public static function getLastLoadedHtml() 136 | { 137 | return self::$_lastHtml; 138 | } 139 | 140 | public static function getLastLoadedUrl() 141 | { 142 | return self::$_lastLoadedUrl; 143 | } 144 | 145 | /** 146 | * Ensure the URL is set, return default otherwise 147 | * @return string 148 | */ 149 | public static function getUrl($url = false) 150 | { 151 | $url = false !== $url ? $url : self::$_url; 152 | return $url; 153 | } 154 | 155 | public function setUrl($url) 156 | { 157 | if (false !== Helper\Url::isRfc($url)) { 158 | self::$_url = $url; 159 | self::$_host = Helper\Url::parseHost($url); 160 | } 161 | else { 162 | throw new E('Invalid URL!'); 163 | exit(); 164 | } 165 | return true; 166 | } 167 | 168 | public static function getHost($url = false) 169 | { 170 | return Helper\Url::parseHost(self::getUrl($url)); 171 | } 172 | 173 | public static function getDomain($url = false) 174 | { 175 | return 'http://' . self::getHost($url = false); 176 | } 177 | 178 | /** 179 | * @return DOMDocument 180 | */ 181 | protected static function _getDOMDocument($html) { 182 | $doc = new \DOMDocument; 183 | @$doc->loadHtml($html); 184 | return $doc; 185 | } 186 | 187 | /** 188 | * @return DOMXPath 189 | */ 190 | protected static function _getDOMXPath($doc) { 191 | $xpath = new \DOMXPath($doc); 192 | return $xpath; 193 | } 194 | 195 | /** 196 | * @return HTML string 197 | */ 198 | protected static function _getPage($url) { 199 | $url = self::getUrl($url); 200 | if (self::getLastLoadedUrl() == $url) { 201 | return self::getLastLoadedHtml(); 202 | } 203 | 204 | $html = Helper\HttpRequest::sendRequest($url); 205 | if ($html) { 206 | self::$_lastLoadedUrl = $url; 207 | self::_setHtml($html); 208 | return $html; 209 | } 210 | else { 211 | self::noDataDefaultValue(); 212 | } 213 | } 214 | 215 | protected static function _setHtml($str) 216 | { 217 | self::$_lastHtml = $str; 218 | } 219 | 220 | protected static function noDataDefaultValue() 221 | { 222 | return Config\DefaultSettings::DEFAULT_RETURN_NO_DATA; 223 | } 224 | 225 | /** 226 | * @return Proxy address 227 | */ 228 | public static function getCurloptProxy() 229 | { 230 | return self::$_curlopt_proxy; 231 | } 232 | 233 | /** 234 | * @param Proxy address $curlopt_proxy 235 | */ 236 | public static function setCurloptProxy($curlopt_proxy) 237 | { 238 | self::$_curlopt_proxy = $curlopt_proxy; 239 | } 240 | 241 | /** 242 | * @return Proxy auth 243 | */ 244 | public static function getCurloptProxyuserpwd() 245 | { 246 | return self::$_curlopt_proxyuserpwd; 247 | } 248 | 249 | /** 250 | * @param Proxy auth $curlopt_proxyuserpwd 251 | */ 252 | public static function setCurloptProxyuserpwd($curlopt_proxyuserpwd) 253 | { 254 | self::$_curlopt_proxyuserpwd = $curlopt_proxyuserpwd; 255 | } 256 | 257 | /** 258 | * @return Useragent string 259 | */ 260 | public static function getUserAgent() 261 | { 262 | return self::$_ua; 263 | } 264 | 265 | /** 266 | * @param Useragent string $ua 267 | */ 268 | public static function setUserAgent($ua) 269 | { 270 | self::$_ua = $ua; 271 | } 272 | } 273 | -------------------------------------------------------------------------------- /SEOstats/Services/Social.php: -------------------------------------------------------------------------------- 1 | 9 | * @copyright Copyright (c) 2010 - present Stephan Schmitz 10 | * @license http://eyecatchup.mit-license.org/ MIT License 11 | * @updated 2014/01/19 12 | */ 13 | 14 | use SEOstats\SEOstats as SEOstats; 15 | use SEOstats\Config as Config; 16 | use SEOstats\Helper as Helper; 17 | 18 | class Social extends SEOstats 19 | { 20 | /** 21 | * For backward compatibility 22 | * @deprecated 23 | */ 24 | public static function getGoogleShares($url = false) { 25 | return self::getGooglePlusShares($url); 26 | } 27 | /** 28 | * Returns the total count of +1s for $url on Google+. 29 | * 30 | * @access public 31 | * @param url string The URL to check. 32 | * @return integer Returns the total count of Plus Ones for a URL. 33 | */ 34 | public static function getGooglePlusShares($url = false) 35 | { 36 | $url = parent::getUrl($url); 37 | $dataUrl = sprintf(Config\Services::GOOGLE_PLUSONE_URL, urlencode($url)); 38 | $html = parent::_getPage($dataUrl); 39 | @preg_match_all('/window\.__SSR\s\=\s\{c:\s(\d+?)\./', $html, $match, PREG_SET_ORDER); 40 | 41 | return (1 === sizeof($match) && 2 === sizeof($match[0])) ? intval($match[0][1]) : parent::noDataDefaultValue(); 42 | } 43 | 44 | /** 45 | * Returns an array of interaction counts (shares, likes, comments, clicks) for $url on Facebook. 46 | * 47 | * @access public 48 | * @link http://developers.facebook.com/docs/reference/fql/link_stat/ 49 | * @param url string The URL to check. 50 | * @return array Returns an array of total counts for 1. all Facebook interactions, 51 | * 2. FB shares, 3. FB likes, 4. FB comments and 5. outgoing clicks for a URL. 52 | */ 53 | public static function getFacebookShares($url = false) 54 | { 55 | $url = parent::getUrl($url); 56 | $fql = sprintf('SELECT total_count, share_count, like_count, comment_count, commentsbox_count, click_count FROM link_stat WHERE url="%s"', $url); 57 | $dataUrl = sprintf(Config\Services::FB_LINKSTATS_URL, rawurlencode($fql)); 58 | 59 | $jsonData = parent::_getPage($dataUrl); 60 | $phpArray = Helper\Json::decode($jsonData, true); 61 | 62 | return isset($phpArray[0]) ? $phpArray[0] : parent::noDataDefaultValue(); 63 | } 64 | 65 | /** 66 | * Returns the total count of mentions of $url on Twitter. 67 | * 68 | * @access public 69 | * @param url string The URL to check. 70 | * @return integer Returns the total count of Twitter mentions for a URL. 71 | * @link https://dev.twitter.com/discussions/5653#comment-11514 72 | */ 73 | public static function getTwitterShares($url = false) 74 | { 75 | $url = parent::getUrl($url); 76 | $dataUrl = sprintf(Config\Services::TWEETCOUNT_URL, urlencode($url)); 77 | 78 | $jsonData = parent::_getPage($dataUrl); 79 | $phpArray = Helper\Json::decode($jsonData, true); 80 | 81 | return isset($phpArray['count']) ? intval($phpArray['count']) : parent::noDataDefaultValue(); 82 | } 83 | 84 | /** 85 | * Returns the total count of shares for $url via Delicious. 86 | * 87 | * @access public 88 | * @param url string The URL to check. 89 | * @return integer Returns the total count of URL shares. 90 | */ 91 | public static function getDeliciousShares($url = false) 92 | { 93 | $url = parent::getUrl($url); 94 | $dataUrl = sprintf(Config\Services::DELICIOUS_INFO_URL, urlencode($url)); 95 | 96 | $jsonData = parent::_getPage($dataUrl); 97 | $phpArray = Helper\Json::decode($jsonData, true); 98 | 99 | return isset($phpArray[0]['total_posts']) ? intval($phpArray[0]['total_posts']) : parent::noDataDefaultValue(); 100 | } 101 | 102 | /** 103 | * Returns the Top10 tags for $url from Delicious. 104 | * 105 | * @access public 106 | * @param url string The URL to check. 107 | * @return array Returns the top ten delicious tags for a URL (if exist; else an empty array). 108 | */ 109 | public static function getDeliciousTopTags($url = false) 110 | { 111 | $url = parent::getUrl($url); 112 | $dataUrl = sprintf(Config\Services::DELICIOUS_INFO_URL, urlencode($url)); 113 | 114 | $jsonData = parent::_getPage($dataUrl); 115 | $phpArray = Helper\Json::decode($jsonData, true); 116 | 117 | $ret = array(); 118 | if (isset($phpArray[0]['top_tags']) && 0 < sizeof($phpArray[0]['top_tags'])) { 119 | foreach($phpArray[0]['top_tags'] as $k => $v) { 120 | $ret[] = $k; 121 | } 122 | } 123 | return $ret; 124 | } 125 | 126 | /** 127 | * Returns the total count of shares for $url via Digg. 128 | * 129 | * @access public 130 | * @param url string The URL to check. 131 | * @return integer Returns the total count of URL shares. 132 | */ 133 | public static function getDiggShares($url = false) 134 | { 135 | $url = parent::getUrl($url); 136 | $dataUrl = sprintf(Config\Services::DIGG_INFO_URL, urlencode($url)); 137 | 138 | $jsonData = parent::_getPage($dataUrl); 139 | $phpArray = Helper\Json::decode(substr($jsonData, 2, -2), true); 140 | 141 | return isset($phpArray['diggs']) ? intval($phpArray['diggs']) : parent::noDataDefaultValue(); 142 | } 143 | 144 | /** 145 | * Returns the total count of shares for $url via LinkedIn. 146 | * 147 | * @access public 148 | * @param url string The URL to check. 149 | * @return integer Returns the total count of URL shares. 150 | */ 151 | public static function getLinkedInShares($url = false) 152 | { 153 | $url = parent::getUrl($url); 154 | $dataUrl = sprintf(Config\Services::LINKEDIN_INFO_URL, urlencode($url)); 155 | 156 | $jsonData = parent::_getPage($dataUrl); 157 | $phpArray = Helper\Json::decode(substr($jsonData, 2, -2), true); 158 | 159 | return isset($phpArray['count']) ? intval($phpArray['count']) : parent::noDataDefaultValue(); 160 | } 161 | 162 | /** 163 | * Returns the total count of shares for $url via Pinterest. 164 | * 165 | * @access public 166 | * @param url string The URL to check. 167 | * @return integer Returns the total count of URL shares. 168 | */ 169 | public static function getPinterestShares($url = false) 170 | { 171 | $url = parent::getUrl($url); 172 | $dataUrl = sprintf(Config\Services::PINTEREST_INFO_URL, urlencode($url)); 173 | 174 | $jsonData = parent::_getPage($dataUrl); 175 | $phpArray = Helper\Json::decode(substr($jsonData, 2, -1), true); 176 | 177 | return isset($phpArray['count']) ? intval($phpArray['count']) : parent::noDataDefaultValue(); 178 | } 179 | 180 | /** 181 | * Returns the total count of shares for $url via StumpleUpon. 182 | * 183 | * @access public 184 | * @param url string The URL to check. 185 | * @return integer Returns the total count of URL shares. 186 | */ 187 | public static function getStumbleUponShares($url = false) 188 | { 189 | $url = parent::getUrl($url); 190 | $dataUrl = sprintf(Config\Services::STUMBLEUPON_INFO_URL, urlencode($url)); 191 | 192 | $jsonData = parent::_getPage($dataUrl); 193 | $phpArray = Helper\Json::decode($jsonData, true); 194 | 195 | return isset($phpArray['result']['in_index']) && true == $phpArray['result']['in_index'] 196 | ? intval($phpArray['result']['views']) : parent::noDataDefaultValue(); 197 | } 198 | 199 | /** 200 | * Returns the total count of shares for $url via VKontakte. 201 | * 202 | * @access public 203 | * @param url string The URL to check. 204 | * @return integer Returns the total count of URL shares. 205 | */ 206 | public static function getVKontakteShares($url = false) 207 | { 208 | $url = parent::getUrl($url); 209 | $dataUrl = sprintf(Config\Services::VKONTAKTE_INFO_URL, urlencode($url)); 210 | 211 | $htmlData = parent::_getPage($dataUrl); 212 | @preg_match_all('#^VK\.Share\.count\(1, (\d+)\);$#si', $htmlData, $matches); 213 | 214 | return isset($matches[1][0]) ? intval($matches[1][0]) : parent::noDataDefaultValue(); 215 | } 216 | 217 | /** 218 | * Returns an array of interaction counts (shares, comments, clicks, reach) for host of $url on Xing. 219 | * 220 | * @access public 221 | * @param url string The URL to check. 222 | * @return array Returns URL shares, comments, clicks and 'reach'. 223 | * @link https://blog.xing.com/2012/01/xing-share-button/ Return values explained (German) 224 | */ 225 | public static function getXingShares($url = false) 226 | { 227 | $host = parent::getHost($url); 228 | $dataUrl = sprintf(Config\Services::XING_SHAREBUTTON_URL, urlencode($host)); 229 | 230 | $htmlData = parent::_getPage($dataUrl); 231 | @preg_match_all('/\r?\n(\d+)\r?\n/s', $htmlData, $matches); 232 | 233 | if (isset($matches[1]) && 4 == sizeof($matches[1])) { 234 | return array( 235 | 'shares' => intval($matches[1][0]), 236 | 'comments' => intval($matches[1][1]), 237 | 'clicks' => intval($matches[1][2]), 238 | 'reach' => intval($matches[1][3]), 239 | ); 240 | } 241 | 242 | return parent::noDataDefaultValue(); 243 | } 244 | } 245 | -------------------------------------------------------------------------------- /SEOstats/Services/SemRush.php: -------------------------------------------------------------------------------- 1 | 9 | * @copyright Copyright (c) 2010 - present Stephan Schmitz 10 | * @license http://eyecatchup.mit-license.org/ MIT License 11 | * @updated 2013/08/14 12 | */ 13 | 14 | use SEOstats\Common\SEOstatsException as E; 15 | use SEOstats\SEOstats as SEOstats; 16 | use SEOstats\Config as Config; 17 | use SEOstats\Helper as Helper; 18 | 19 | class SemRush extends SEOstats 20 | { 21 | public static function getDBs() 22 | { 23 | return array( 24 | "au", # Google.com.au (Australia) 25 | "br", # Google.com.br (Brazil) 26 | "ca", # Google.ca (Canada) 27 | "de", # Google.de (Germany) 28 | "es", # Google.es (Spain) 29 | "fr", # Google.fr (France) 30 | "it", # Google.it (Italy) 31 | "ru", # Google.ru (Russia) 32 | "uk", # Google.co.uk (United Kingdom) 33 | 'us', # Google.com (United States) 34 | "us.bing" # Bing.com 35 | ); 36 | } 37 | 38 | public static function getParams() 39 | { 40 | return array( 41 | "DomainReports" => array( 42 | "Ac" => "Estimated expenses the site has for advertising in Ads (per month).", 43 | "Ad" => "Number of Keywords this site has in the TOP20 Ads results.", 44 | "At" => "Estimated number of visitors coming from Ads (per month).", 45 | "Dn" => "The requested site name.", 46 | "Dt" => "The date when the report data was computed (formatted as YYYYmmdd).", 47 | "Np" => "The number of keywords for which the site is displayed in search results next to the analyzed site.", 48 | "Oa" => "Estimated number of potential ad/traffic buyers.", 49 | "Oc" => "Estimated cost of purchasing the same number of visitors through Ads.", 50 | "Oo" => "Estimated number of competitors in organic search.", 51 | "Or" => "Number of Keywords this site has in the TOP20 organic results.", 52 | "Ot" => "Estimated number of visitors coming from the first 20 search results (per month).", 53 | "Rk" => "The SEMRush Rank (rating of sites by the number of visitors coming from the first 20 search results)." 54 | ), 55 | "OrganicKeywordReports" => array( 56 | "Co" => "Competition of advertisers for that term (the higher the number - the greater the competition).", 57 | "Cp" => "Average price of a click on an Ad for this search query (in U.S. dollars).", 58 | "Nr" => "The number of search results - how many results does the search engine return for this query.", 59 | "Nq" => "Average number of queries for the keyword per month (for the corresponding local version of search engine).", 60 | "Ph" => "The search query the site has within the first 20 search results.", 61 | "Po" => "The site's position for the search query (at the moment of data collection).", 62 | "Pp" => "The site's position for the search query (at the time of prior data collection).", 63 | "Tc" => "The estimated value of the organic traffic generated by the query as compared to the cost of purchasing the same volume of traffic through Ads.", 64 | "Tr" => "The ratio comparing the number of visitors coming to the site from this search request to all visitors to the site from search results.", 65 | "Ur" => "URL of a page on the site displayed in search results for this query (landing page)." 66 | ) 67 | ); 68 | } 69 | 70 | /** 71 | * Returns the SEMRush main report data. 72 | * (Only main report is public available.) 73 | * 74 | * @access public 75 | * @param url string Domain name only, eg. "ebay.com" (/wo quotes). 76 | * @param db string Optional: The database to use. Valid values are: 77 | * au, br, ca, de, es, fr, it, ru, uk, us, us.bing (us is default) 78 | * @return array Returns an array containing the main report data. 79 | * @link http://www.semrush.com/api.html 80 | */ 81 | public static function getDomainRank($url = false, $db = false) 82 | { 83 | $data = self::getBackendData($url, $db, 'domain_rank'); 84 | 85 | return is_array($data) ? $data['rank']['data'][0] : $data; 86 | } 87 | 88 | public static function getDomainRankHistory($url = false, $db = false) 89 | { 90 | $data = self::getBackendData($url, $db, 'domain_rank_history'); 91 | 92 | return is_array($data) ? $data['rank_history'] : $data; 93 | } 94 | 95 | public static function getOrganicKeywords($url = false, $db = false) 96 | { 97 | return static::getWidgetData($url, $db, 'organic', 'organic'); 98 | } 99 | 100 | public static function getCompetitors($url = false, $db = false) 101 | { 102 | return static::getWidgetData($url, $db, 'organic_organic', 'organic_organic'); 103 | } 104 | 105 | public static function getDomainGraph($reportType = 1, $url = false, $db = false, $w = 400, $h = 300, $lc = 'e43011', $dc = 'e43011', $lang = 'en', $html = true) 106 | { 107 | $domain = static::getDomainFromUrl($url); 108 | $database = static::getValidDatabase($db); 109 | 110 | static::guardValidArgsForGetDomainGraph($reportType, $w, $h, $lang); 111 | 112 | $imgUrl = sprintf(Config\Services::SEMRUSH_GRAPH_URL, 113 | $domain, $database, $reportType, $w, $h, $lc, $dc, $lang); 114 | 115 | if (! $html) { 116 | return $imgUrl; 117 | } else { 118 | $imgTag = 'SEMRush Domain Trend Graph for %s'; 119 | return sprintf($imgTag, $imgUrl, $w, $h, $domain); 120 | } 121 | } 122 | 123 | protected static function getApiData($url) 124 | { 125 | $json = static::_getPage($url); 126 | return Helper\Json::decode($json, true); 127 | } 128 | 129 | protected static function getSemRushDatabase($db) 130 | { 131 | return false !== $db 132 | ? $db 133 | : Config\DefaultSettings::SEMRUSH_DB; 134 | } 135 | 136 | protected static function guardDomainIsValid($domain) 137 | { 138 | if (false == $domain) { 139 | self::exc('Invalid domain name.'); 140 | } 141 | } 142 | 143 | protected static function guardDatabaseIsValid($database) 144 | { 145 | if (false === $database) { 146 | self::exc('db'); 147 | } 148 | } 149 | 150 | protected static function guardValidArgsForGetDomainGraph($reportType, $width, $height, $lang) 151 | { 152 | if ($reportType > 5 || $reportType < 1) { 153 | self::exc('Report type must be between 1 (one) and 5 (five).'); 154 | } 155 | 156 | if ($width > 400 || $width < 200) { 157 | self::exc('Image width must be between 200 and 400 px.'); 158 | } 159 | 160 | if ($height > 300 || $height < 150) { 161 | self::exc('Image height must be between 150 and 300 px.'); 162 | } 163 | 164 | if (strlen($lang) != 2) { 165 | self::exc('You must specify a valid language code.'); 166 | } 167 | } 168 | 169 | protected static function getBackendData($url, $db, $reportType) 170 | { 171 | $db = false !== $db ? $db : Config\DefaultSettings::SEMRUSH_DB; 172 | $dataUrl = self::getBackendUrl($url, $db, $reportType); 173 | $data = self::getApiData($dataUrl); 174 | 175 | if (!is_array($data)) { 176 | $data = self::getApiData(str_replace('.backend.', '.api.', $dataUrl)); 177 | if (!is_array($data)) { 178 | return parent::noDataDefaultValue(); 179 | } 180 | } 181 | 182 | return $data; 183 | } 184 | 185 | protected static function getBackendUrl($url, $db, $reportType) 186 | { 187 | $domain = static::getDomainFromUrl($url); 188 | $database = static::getValidDatabase($db); 189 | 190 | $backendUrl = Config\Services::SEMRUSH_BE_URL; 191 | return sprintf($backendUrl, $database, $reportType, $domain); 192 | } 193 | 194 | protected static function getWidgetUrl($url, $db, $reportType) 195 | { 196 | $domain = static::getDomainFromUrl($url); 197 | $database = static::getValidDatabase($db); 198 | 199 | $widgetUrl = Config\Services::SEMRUSH_WIDGET_URL; 200 | return sprintf($widgetUrl, $reportType, $database, $domain); 201 | } 202 | 203 | protected static function getWidgetData($url, $db, $reportType, $valueKey) 204 | { 205 | $db = false !== $db ? $db : Config\DefaultSettings::SEMRUSH_DB; 206 | $dataUrl = self::getWidgetUrl($url, $db, $reportType); 207 | $data = self::getApiData($dataUrl); 208 | 209 | return !is_array($data) ? parent::noDataDefaultValue() : $data[ $valueKey ]; 210 | } 211 | 212 | protected static function checkDatabase($db) 213 | { 214 | return !in_array($db, self::getDBs()) ? false : $db; 215 | } 216 | 217 | /** 218 | * 219 | * @throws E 220 | */ 221 | protected static function exc($err) 222 | { 223 | $e = ($err == 'db') ? "Invalid database. Choose one of: " . 224 | substr( implode(", ", self::getDBs()), 0, -2) : $err; 225 | throw new E($e); 226 | exit(0); 227 | } 228 | 229 | protected static function getDomainFromUrl($url) 230 | { 231 | $url = parent::getUrl($url); 232 | $domain = Helper\Url::parseHost($url); 233 | static::guardDomainIsValid($domain); 234 | 235 | return $domain; 236 | } 237 | 238 | protected static function getValidDatabase($db) 239 | { 240 | $db = self::getSemRushDatabase($db); 241 | $database = self::checkDatabase($db); 242 | static::guardDatabaseIsValid($database); 243 | 244 | return $database; 245 | } 246 | } 247 | -------------------------------------------------------------------------------- /SEOstats/Services/Alexa.php: -------------------------------------------------------------------------------- 1 | 9 | * @copyright Copyright (c) 2010 - present Stephan Schmitz 10 | * @license http://eyecatchup.mit-license.org/ MIT License 11 | * @updated 2013/08/17 12 | */ 13 | 14 | use SEOstats\SEOstats as SEOstats; 15 | use SEOstats\Config as Config; 16 | use SEOstats\Helper as Helper; 17 | 18 | class Alexa extends SEOstats 19 | { 20 | /** 21 | * Used for cache 22 | * @var DOMXPath 23 | */ 24 | protected static $_xpath = false; 25 | 26 | protected static $_rankKeys = array( 27 | '1d' => 0, 28 | '7d' => 0, 29 | '1m' => 0, 30 | '3m' => 0, 31 | ); 32 | 33 | /** 34 | * Get yesterday's rank 35 | * @return int 36 | */ 37 | public static function getDailyRank($url = false) 38 | { 39 | self::setRankingKeys($url); 40 | if (0 == self::$_rankKeys['1d']) { 41 | return parent::noDataDefaultValue(); 42 | } 43 | 44 | $xpath = self::_getXPath($url); 45 | $nodes = @$xpath->query("//*[@id='rank']/table/tr[" . self::$_rankKeys['1d'] . "]/td[1]"); 46 | 47 | return !$nodes->item(0) ? parent::noDataDefaultValue() : 48 | self::retInt( strip_tags($nodes->item(0)->nodeValue) ); 49 | } 50 | 51 | /** 52 | * For backward compatibility 53 | * @deprecated 54 | */ 55 | public static function getWeekRank($url = false) { 56 | return self::getWeeklyRank($url); 57 | } 58 | /** 59 | * Get the average rank over the last 7 days 60 | * @return int 61 | */ 62 | public static function getWeeklyRank($url = false) 63 | { 64 | self::setRankingKeys($url); 65 | if (0 == self::$_rankKeys['7d']) { 66 | return parent::noDataDefaultValue(); 67 | } 68 | 69 | $xpath = self::_getXPath($url); 70 | $nodes = @$xpath->query("//*[@id='rank']/table/tr[" . self::$_rankKeys['7d'] . "]/td[1]"); 71 | 72 | return !$nodes->item(0) ? parent::noDataDefaultValue() : 73 | self::retInt( strip_tags($nodes->item(0)->nodeValue) ); 74 | } 75 | 76 | /** 77 | * For backward compatibility 78 | * @deprecated 79 | */ 80 | public static function getMonthRank($url = false) { 81 | return self::getMonthlyRank($url); 82 | } 83 | /** 84 | * Get the average rank over the last month 85 | * @return int 86 | */ 87 | public static function getMonthlyRank($url = false) 88 | { 89 | self::setRankingKeys($url); 90 | if (0 == self::$_rankKeys['1m']) { 91 | return parent::noDataDefaultValue(); 92 | } 93 | 94 | $xpath = self::_getXPath($url); 95 | $nodes = @$xpath->query("//*[@id='rank']/table/tr[" . self::$_rankKeys['1m'] . "]/td[1]"); 96 | 97 | return !$nodes->item(0) ? parent::noDataDefaultValue() : 98 | self::retInt( strip_tags($nodes->item(0)->nodeValue) ); 99 | } 100 | 101 | /** 102 | * For backward compatibility 103 | * @deprecated 104 | */ 105 | public static function getQuarterRank($url = false) { 106 | return self::getGlobalRank($url); 107 | } 108 | /** 109 | * Get the average rank over the last 3 months 110 | * @return int 111 | */ 112 | public static function getGlobalRank($url = false) 113 | { 114 | /* 115 | self::setRankingKeys($url); 116 | if (0 == self::$_rankKeys['3m']) { 117 | return parent::noDataDefaultValue(); 118 | } 119 | */ 120 | 121 | $xpath = self::_getXPath($url); 122 | 123 | $xpathQueryList = array( 124 | "//*[@id='traffic-rank-content']/div/span[2]/div[1]/span/span/div/strong", 125 | "//*[@id='traffic-rank-content']/div/span[2]/div[1]/span/span/div/strong/a" 126 | ); 127 | 128 | return static::parseDomByXpathsToIntegerWithoutTags($xpath, $xpathQueryList); 129 | } 130 | 131 | /** 132 | * Get the average rank over the week 133 | * @return int 134 | */ 135 | public static function setRankingKeys($url = false) 136 | { 137 | $xpath = self::_getXPath($url); 138 | $nodes = @$xpath->query("//*[@id='rank']/table/tr"); 139 | 140 | if (5 == $nodes->length) { 141 | self::$_rankKeys = array( 142 | '1d' => 2, 143 | '7d' => 3, 144 | '1m' => 4, 145 | '3m' => 5, 146 | ); 147 | } 148 | else if (4 == $nodes->length) { 149 | self::$_rankKeys = array( 150 | '1d' => 0, 151 | '7d' => 2, 152 | '1m' => 3, 153 | '3m' => 4, 154 | ); 155 | } 156 | else if (3 == $nodes->length) { 157 | self::$_rankKeys = array( 158 | '1d' => 0, 159 | '7d' => 0, 160 | '1m' => 2, 161 | '3m' => 3, 162 | ); 163 | } 164 | else if (2 == $nodes->length) { 165 | self::$_rankKeys = array( 166 | '1d' => 0, 167 | '7d' => 0, 168 | '1m' => 0, 169 | '3m' => 2, 170 | ); 171 | } 172 | } 173 | 174 | public static function getCountryRank($url = false) 175 | { 176 | $xpath = self::_getXPath($url); 177 | $node1 = self::parseDomByXpaths($xpath, array( 178 | "//*[@id='traffic-rank-content']/div/span[2]/div[2]/span/span/h4/a", 179 | "//*[@id='traffic-rank-content']/div/span[2]/div[2]/span/span/h4/strong/a", 180 | )); 181 | 182 | $node2 = self::parseDomByXpaths($xpath, array( 183 | "//*[@id='traffic-rank-content']/div/span[2]/div[2]/span/span/div/strong/a", 184 | "//*[@id='traffic-rank-content']/div/span[2]/div[2]/span/span/div/strong", 185 | )); 186 | 187 | $node3 = self::parseDomByXpaths($xpath, array( 188 | "//*[@id='traffic-rank-content']/div/span[2]/div[2]/span/span/h4/a/@href", 189 | "//*[@id='traffic-rank-content']/div/span[2]/div[2]/span/span/h4/strong/a/@href", 190 | )); 191 | 192 | if (!is_null($node2) && $node2->item(0)) { 193 | $rank = self::retInt(strip_tags($node2->item(0)->nodeValue)); 194 | $country_code = str_replace("/topsites/countries/", "", $node3->item(0)->nodeValue); 195 | if ($node1->item(0) && 0 != $rank) { 196 | return array( 197 | 'rank' => $rank, 198 | 'country' => $node1->item(0)->nodeValue, 199 | 'country_code' => $country_code, 200 | ); 201 | } 202 | } 203 | 204 | return parent::noDataDefaultValue(); 205 | } 206 | 207 | public static function getBacklinkCount($url = false) 208 | { 209 | $xpath = self::_getXPath($url); 210 | 211 | $queryList = array( 212 | "//section[@id='linksin-panel-content']/div/span/div/span", 213 | "//*[@id='linksin_div']/section/div/div[1]/span" 214 | ); 215 | 216 | return static::parseDomByXpathsToInteger($xpath, $queryList); 217 | } 218 | 219 | public static function getPageLoadTime($url = false) 220 | { 221 | $xpath = self::_getXPath($url); 222 | 223 | $queryList = array( 224 | "//section[@class='row-fluid panel-wrapper '][9]/section/p", 225 | "//*[@id='section-load']/div/section/p" 226 | ); 227 | 228 | return static::parseDomByXpathsWithoutTags($xpath, $queryList); 229 | } 230 | 231 | /** 232 | * @access public 233 | * @param integer $type Specifies the graph type. Valid values are 1 to 6. 234 | * @param integer $width Specifies the graph width (in px). 235 | * @param integer $height Specifies the graph height (in px). 236 | * @param integer $period Specifies the displayed time period. Valid values are 1 to 12. 237 | * @return string Returns a string, containing the HTML code of an image, showing Alexa Statistics as Graph. 238 | */ 239 | public static function getTrafficGraph($type = 1, $url = false, $w = 660, $h = 330, $period = 1, $html = true) 240 | { 241 | $url = self::getUrl($url); 242 | $domain = Helper\Url::parseHost($url); 243 | 244 | switch($type) { 245 | case 1: $gtype = 't'; break; 246 | case 2: $gtype = 'p'; break; 247 | case 3: $gtype = 'u'; break; 248 | case 4: $gtype = 's'; break; 249 | case 5: $gtype = 'b'; break; 250 | case 6: $gtype = 'q'; break; 251 | default: break; 252 | } 253 | 254 | $imgUrl = sprintf(Config\Services::ALEXA_GRAPH_URL, $gtype, $w, $h, $period, $domain); 255 | $imgTag = 'Alexa Statistics Graph for %s'; 256 | 257 | return !$html ? $imgUrl : sprintf($imgTag, $imgUrl, $w, $h, $domain); 258 | } 259 | 260 | /** 261 | * @return DOMXPath 262 | */ 263 | protected static function _getXPath($url) { 264 | $url = parent::getUrl($url); 265 | if (parent::getLastLoadedUrl() == $url && self::$_xpath) { 266 | return self::$_xpath; 267 | } 268 | 269 | $html = static::_getAlexaPage($url); 270 | $doc = parent::_getDOMDocument($html); 271 | $xpath = parent::_getDOMXPath($doc); 272 | 273 | self::$_xpath = $xpath; 274 | 275 | return $xpath; 276 | } 277 | 278 | protected static function _getAlexaPage($url) 279 | { 280 | $domain = Helper\Url::parseHost($url); 281 | $dataUrl = sprintf(Config\Services::ALEXA_SITEINFO_URL, $domain); 282 | $html = static::_getPage($dataUrl); 283 | return $html; 284 | } 285 | 286 | protected static function retInt($str) 287 | { 288 | $strim = trim(str_replace(',', '', $str)); 289 | $intStr = 0 < strlen($strim) ? $strim : '0'; 290 | return intval($intStr); 291 | } 292 | 293 | /** 294 | * 295 | * @return mixed nodeValue 296 | */ 297 | protected static function parseDomByXpaths($xpathDom, $xpathQueryList) { 298 | 299 | foreach ( $xpathQueryList as $query ) { 300 | $nodes = @$xpathDom->query($query); 301 | 302 | if ( $nodes->length != 0 ) { 303 | return $nodes; 304 | } 305 | } 306 | 307 | return null; 308 | } 309 | 310 | /** 311 | * 312 | * @return mixed nodeValue 313 | */ 314 | protected static function parseDomByXpathsGetValue($xpathDom, $xpathQueryList) 315 | { 316 | $nodes = static::parseDomByXpaths($xpathDom, $xpathQueryList); 317 | 318 | return ($nodes) ? $nodes->item(0)->nodeValue : null; 319 | } 320 | 321 | /** 322 | * 323 | * @return mixed nodeValue 324 | */ 325 | protected static function parseDomByXpathsToInteger($xpathDom, $xpathQueryList) 326 | { 327 | $nodeValue = static::parseDomByXpathsGetValue($xpathDom, $xpathQueryList); 328 | 329 | if ($nodeValue === null) { 330 | return parent::noDataDefaultValue(); 331 | } 332 | return self::retInt( $nodeValue ); 333 | } 334 | 335 | /** 336 | * 337 | * @return mixed nodeValue 338 | */ 339 | protected static function parseDomByXpathsWithoutTags($xpathDom, $xpathQueryList) 340 | { 341 | 342 | $nodeValue = static::parseDomByXpathsGetValue($xpathDom, $xpathQueryList); 343 | 344 | if ($nodeValue === null) { 345 | return parent::noDataDefaultValue(); 346 | } 347 | 348 | return strip_tags($nodeValue); 349 | } 350 | 351 | /** 352 | * 353 | * @return mixed nodeValue 354 | */ 355 | protected static function parseDomByXpathsToIntegerWithoutTags($xpathDom, $xpathQueryList) 356 | { 357 | $nodeValue = static::parseDomByXpathsGetValue($xpathDom, $xpathQueryList); 358 | 359 | if ($nodeValue === null) { 360 | return parent::noDataDefaultValue(); 361 | } 362 | 363 | return self::retInt(strip_tags($nodeValue)); 364 | } 365 | } 366 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SEOstats: SEO metrics library for PHP 2 | 3 | [![License](https://poser.pugx.org/seostats/seostats/license "SEOstats is licensed under the M.I.T.")](https://github.com/eyecatchup/SEOstats) [![Scrutinizer Code Quality Rating](https://scrutinizer-ci.com/g/eyecatchup/SEOstats/badges/quality-score.png?b=master "Scrutinizer Code Quality Rating: Very Good!")](https://scrutinizer-ci.com/g/eyecatchup/SEOstats/ "'Very good' code quality, says Scrutinizer C.I.") [![Build Status](https://travis-ci.org/eyecatchup/SEOstats.svg?branch=master "Travis C.I. Build Status")](https://travis-ci.org/eyecatchup/SEOstats) [![Scrutinizer Test Coverage Report](https://scrutinizer-ci.com/g/eyecatchup/SEOstats/badges/coverage.png?b=master "Scrutinizer Test Coverage Report")](https://scrutinizer-ci.com/g/eyecatchup/SEOstats/code-structure/master?elementType=class&orderField=test_coverage&order=desc&changesExpanded=0 "Test coverage report by Scrutinizer C.I.") [![Latest Stable Version](https://poser.pugx.org/seostats/seostats/v/stable "Latest Stable Version")](https://packagist.org/packages/seostats/seostats) [![Latest Unstable Version](https://poser.pugx.org/seostats/seostats/v/unstable "Latest Unstable Version")](https://packagist.org/packages/seostats/seostats) [![Monthly Downloads](https://poser.pugx.org/seostats/seostats/d/monthly "That many people downloaded SEOstats from Github or Packagist this month.")](https://packagist.org/packages/seostats/seostats) 4 | 5 | SEOstats is _the_ open source PHP library to get SEO-relevant website metrics. 6 | 7 | SEOstats is used to gather metrics such as detailed searchindex & backlink data, keyword & traffic statistics, website trends, page authority, social visibility, Google Pagerank, Alexa Trafficrank and more. 8 | 9 | It offers over 50 different methods to fetch data from sources like Alexa, Google, Mozscape (by Moz - f.k.a. Seomoz), SEMRush, Open-Site-Explorer, Sistrix, Facebook or Twitter. 10 | 11 | A variety of *(private as well as enterprise)* SEO tools have been built using SEOstats. 12 | 13 | ## Dependencies 14 | 15 | SEOstats requires PHP version 5.3 or greater and the PHP5-CURL and PHP5-JSON extensions. 16 | 17 | ## Installation 18 | 19 | The recommended way to install SEOstats is [through composer](http://getcomposer.org). 20 | To install SEOstats, just create the following `composer.json` file 21 | 22 | { 23 | "require": { 24 | "seostats/seostats": "dev-master" 25 | } 26 | } 27 | and run the `php composer.phar install` (Windows: `composer install`) command in path of the `composer.json`. 28 | 29 | #### Step-by-step example: 30 | 31 | If you haven't installed composer yet, here's the easiest way to do so: 32 | ``` 33 | # Download the composer installer and execute it with PHP: 34 | user@host:~/> curl -sS https://getcomposer.org/installer | php 35 | 36 | # Copy composer.phar to where your local executables live: 37 | user@host:~/> mv /path/given/by/composer-installer/composer.phar /usr/local/bin/composer.phar 38 | 39 | # Alternatively: For ease of use, you can add an alias to your bash profile: 40 | # (Note, you need to re-login your terminal for the change to take effect.) 41 | user@host:~/> echo 'alias composer="php /usr/local/bin/composer.phar"' >> ~/.profile 42 | ``` 43 |
44 | If you have installed composer, follow these steps to install SEOstats: 45 | ``` 46 | # Create a new directory and cd into it: 47 | user@host:~/> mkdir /path/to/seostats && cd /path/to/seostats 48 | 49 | # Create the composer.json for SEOstats: 50 | user@host:/path/to/seostats> echo '{"require":{"seostats/seostats":"dev-master"}}' > composer.json 51 | 52 | # Run the install command: 53 | user@host:/path/to/seostats> composer install 54 | Loading composer repositories with package information 55 | Installing dependencies (including require-dev) 56 | - Installing seostats/seostats (dev-master 4c192e4) 57 | Cloning 4c192e43256c95741cf85d23ea2a0d59a77b7a9a 58 | 59 | Writing lock file 60 | Generating autoload files 61 | 62 | # You're done. For a quick start, you can now 63 | # copy the example files to the install directory: 64 | user@host:/path/to/seostats> cp ./vendor/seostats/seostats/example/*.php ./ 65 | 66 | # Your SEOstats install directory should look like this now: 67 | user@host:/path/to/seostats> ls -1 68 | composer.json 69 | composer.lock 70 | get-alexa-graphs.php 71 | get-alexa-metrics.php 72 | get-google-pagerank.php 73 | get-google-pagespeed-analysis.php 74 | get-google-serps.php 75 | get-opensiteexplorer-metrics.php 76 | get-semrush-graphs.php 77 | get-semrush-metrics.php 78 | get-sistrix-visibilityindex.php 79 | get-social-metrics.php 80 | vendor 81 | ``` 82 |
83 | #### Use SEOstats without composer 84 | 85 | If composer is no option for you, you can still just download the [`SEOstats.zip`](https://github.com/eyecatchup/SEOstats/archive/master.zip) file of the current master branch (version 2.5.2) and extract it. However, currently [there is an issues with autoloading](https://github.com/eyecatchup/SEOstats/issues/49) and you need to follow the instructions in the comments in the example files in order to use SEOstats (or download zip for the development version of SEOstats (2.5.3) [here](https://github.com/eyecatchup/SEOstats/archive/dev-253.zip)). 86 | 87 | ## Usage 88 | 89 | ### TOC 90 | 91 | * Configuration 92 | * Brief Example of Use 93 | * Alexa Methods 94 | * Alexa Traffic Metrics 95 | * Alexa Traffic Graphs 96 | * Google Methods 97 | * Toolbar Pagerank 98 | * Pagespeed Service 99 | * Websearch Index 100 | * SERP Details 101 | * Mozscape Methods 102 | * Open Site Explorer Methods 103 | * SEMRush Methods 104 | * Domain Reports 105 | * Graphs 106 | * Sistrix Methods 107 | * Visibility Index 108 | * Social Media Methods 109 | 110 |
111 | 112 | ### Configuration 113 | There're two configuration files to note: 114 |
    115 |
  1. `./SEOstats/Config/ApiKeys.php`
    116 | Client API Keys (currently only required for Mozscape, Google's Pagespeed Service and Sistrix). 117 |
  2. 118 |
  3. `./SEOstats/Config/DefaultSettings.php`
    119 | Some default settings for querying data (mainly locale related stuff). 120 |
  4. 121 |
122 |
123 | 124 | ### Brief Example of Use 125 | To use the SEOstats methods, you must include one of the Autoloader classes first (For composer installs: `./vendor/autoload.php`; for zip download: `./SEOstats/bootstrap.php`). 126 | 127 | Now, you can create a new SEOstats instance an bind any URL to the instance for further use with any child class. 128 | 129 | ```php 130 | setUrl($url)) { 145 | 146 | echo SEOstats\Alexa::getGlobalRank(); 147 | echo SEOstats\Google::getPageRank(); 148 | } 149 | } 150 | catch (SEOstatsException $e) { 151 | die($e->getMessage()); 152 | } 153 | ``` 154 | 155 | Alternatively, you can call all methods statically passing the URL to the methods directly. 156 | 157 | ```php 158 | getMessage()); 171 | } 172 | ``` 173 | 174 | More detailed examples can be found in the `./example` directory. 175 |
176 | 177 | ## SEOstats Alexa Methods 178 | 179 | ### Alexa Traffic Metrics 180 | ```php 181 | 227 | 228 | ## SEOstats Google Methods 229 | 230 | ### Google Toolbar PageRank 231 | 232 | ```php 233 | 278 | 279 | ## SEOstats Mozscape Methods 280 | 281 | ```php 282 | 304 | 305 | ## SEOstats Open Site Explorer (by MOZ) Methods 306 | 307 | ```php 308 | domainAuthority->result . ' (' . // Int - e.g 42 316 | $ose->domainAuthority->unit . ') - ' . // String - "/100" 317 | $ose->domainAuthority->descr . PHP_EOL; // String - Result value description 318 | 319 | // MOZ Page-Authority Rank - Predicts this page's ranking potential in the search engines 320 | // based on an algorithmic combination of all link metrics. 321 | print "Page-Authority: " . 322 | $ose->pageAuthority->result . ' (' . // Int - e.g 48 323 | $ose->pageAuthority->unit . ') - ' . // String - "/100" 324 | $ose->pageAuthority->descr . PHP_EOL; // String - Result value description 325 | 326 | // Just-Discovered Inbound Links - Number of links to this page found over the past %n days, 327 | // indexed within an hour of being shared on Twitter. 328 | print "Just-Discovered Links: " . 329 | $ose->justDiscovered->result . ' (' . // Int - e.g 140 330 | $ose->justDiscovered->unit . ') - ' . // String - e.g "32 days" 331 | $ose->justDiscovered->descr . PHP_EOL; // String - Result value description 332 | 333 | // Root-Domain Inbound Links - Number of unique root domains (e.g., *.example.com) 334 | // containing at least one linking page to this URL. 335 | print "Linking Root Domains: " . 336 | $ose->linkingRootDomains->result . ' (' . // Int - e.g 210 337 | $ose->linkingRootDomains->unit . ') - ' . // String - "Root Domains" 338 | $ose->linkingRootDomains->descr . PHP_EOL; // String - Result value description 339 | 340 | // Total Links - All links to this page including internal, external, followed, and nofollowed. 341 | print "Total Links: " . 342 | $ose->totalLinks->result . ' (' . // Int - e.g 31571 343 | $ose->totalLinks->unit . ') - ' . // String - "Total Links" 344 | $ose->totalLinks->descr . PHP_EOL; // String - Result value description 345 | ``` 346 |
347 | 348 | ## SEOstats SEMRush Methods 349 | 350 | ### SEMRush Domain Reports 351 | 352 | ```php 353 | 389 | 390 | ## SEOstats Sistrix Methods 391 | 392 | ### Sistrix Visibility Index 393 | 394 | ```php 395 | 401 | 402 | ## SEOstats Social Media Methods 403 | 404 | ### Google+ PlusOnes 405 | 406 | ```php 407 | 457 | 458 | ## License 459 | 460 | (c) 2010 - 2016, Stephan Schmitz eyecatchup@gmail.com 461 | License: MIT, http://eyecatchup.mit-license.org 462 | URL: https://eyecatchup.github.io 463 | -------------------------------------------------------------------------------- /SEOstats/Services/3rdparty/GTB_PageRank.php: -------------------------------------------------------------------------------- 1 | 5 | */ 6 | interface pref 7 | { 8 | const PREFERED_TLD = "com"; 9 | } 10 | /* tbr Toolbar server request interface. 11 | * @package GTB_PageRank 12 | * @author Stephan Schmitz 13 | */ 14 | interface tbr 15 | { 16 | // 2 toolbar server hostnames, as found in the toolbar source code. 17 | const SERVER_HOSTS = '["toolbarqueries.google.","alt1.toolbarqueries.google."]'; 18 | 19 | //138 toolbar server top level domains, as found in the toolbar source code. 20 | const SERVER_TLDS = '["com","ae","com.af","com.ag","com.ai","am","com.ar","as","at","com.au","az","ba","com.bd","be","bg","com.bh","bi","com.bo","com.br","bs","co.bw","com.bz","ca","cd","cg","ch","ci","co.ck","cl","com.co","co.cr","com.cu","cz","de","dj","dk","dm","com.do","com.ec","ee","com.eg","es","com.et","fi","com.fj","fm","fr","co.uk","gg","com.gi","gl","gm","gr","com.gt","com.hk","hn","hr","ht","hu","co.id","ie","co.il","co.im","co.in","is","it","co.je","com.jm","jo","co.jp","co.ke","kg","co.kr","kz","li","lk","co.ls","lt","lu","lv","com.ly","co.ma","mn","ms","com.mt","mu","mw","com.mx","com.my","com.na","com.nf","com.ni","nl","no","com.np","co.nz","com.om","com.pa","com.pe","com.ph","com.pk","pl","pn","com.pr","pt","com.py","com.qa","ro","ru","rw","com.sa","sc","se","com.sg","sh","si","sk","sm","sn","com.sv","co.th","com.tj","tm","to","com.tr","tt","com.tw","com.ua","co.ug","com.uy","co.uz","com.vc","co.ve","vg","co.vi","com.vn","co.za","co.zm"]'; 21 | 22 | // Service request path as found in the toolbar source code. 23 | const SERVER_PATH = "/tbr"; 24 | 25 | // Request query string as found in the toolbar source code. 26 | const QUERY_STRING = "?features=Rank&client=navclient-auto&ch=%s&q=info:%s"; 27 | 28 | // Google's client-specific suggestion of a prefered top level domain (as found in tb source code). 29 | const SUGGEST_TLD_URL = "https://www.google.com/searchdomaincheck?format=domain&sourceid=navclient-ff"; 30 | } 31 | 32 | /* GTB_PageRank Hash a variable-length key into a 32-bit value. 33 | * @package GTB_PageRank 34 | * @author Stephan Schmitz 35 | */ 36 | class GTB_PageRank implements tbr, pref 37 | { 38 | // objects vars 39 | public $QUERY_URL, $URL_HASHES, $PREFERED_TLD, $GTB_SUGESSTED_TLD, $GTB_QUERY_STRINGS; 40 | private $GTB_SERVER; 41 | 42 | /** __construct - Initialize a new object of the class 'GTB_PageRank'. 43 | * @access public 44 | */ 45 | public function __construct($a=NULL) { 46 | if(NULL===$a) { 47 | GTB_Exception::noUrl(); 48 | } 49 | $this->GTB_SERVER = array( // setup the toolbar server vars 50 | "host" => GTB_HELPER::_json_decode(tbr::SERVER_HOSTS), 51 | "tld" => GTB_HELPER::_json_decode(tbr::SERVER_TLDS), 52 | "path" => tbr::SERVER_PATH 53 | ); // setup the client preferences 54 | if (!in_array(self::getPref('tld'), self::getTbrTlds() )) { 55 | GTB_Exception::invalidPref('PREFERED_TLD'); 56 | } else { 57 | $this->PREFERED_TLD = pref::PREFERED_TLD; 58 | $this->GTB_SUGESSTED_TLD = self::getTbrTldSuggestion(); 59 | } 60 | $init = self::setQueryURL($a); // setup the query url 61 | if (TRUE !== $init) { 62 | GTB_Exception::tryAgain(); 63 | } 64 | } 65 | 66 | public function getPageRank() { 67 | $host = $this->GTB_SERVER['host'][0]; 68 | $tld = (strlen($this->GTB_SUGESSTED_TLD) > 0) ? $this->GTB_SUGESSTED_TLD : $this->PREFERED_TLD; 69 | $path = $this->GTB_SERVER['path']; 70 | $tbUrl = 'http://' . $host . $tld . $path; 71 | $qStrings = self::getQueryStrings(); 72 | 73 | for ( $i=0; $i < 3; $i++ ) { 74 | if( !isset($qStrings[$i])) { 75 | break; 76 | } 77 | $PR = self::getToolbarPageRank($tbUrl . $qStrings[$i]); 78 | if ($PR === FALSE) { 79 | continue; 80 | } 81 | return $PR; 82 | } 83 | return 'Failed to generate a valid hash for PR check.'; 84 | } 85 | public function getToolbarPageRank($toolbarUrl) { 86 | $ret = GTB_Request::_get($toolbarUrl); 87 | $pagerank = trim(substr($ret, 9)); 88 | return ($this->isResultValid($pagerank)) ? $pagerank : false; 89 | } 90 | 91 | public function isResultValid($result) { 92 | return preg_match('/^[0-9]/',$result) || $result === ""; 93 | } 94 | 95 | /** getQueryURL - Get the object query url. 96 | * @access public 97 | */ 98 | public function getQueryURL() { 99 | return $this->QUERY_URL; 100 | } 101 | 102 | /** getHash - Get a single hash key value string from object's 'URL_HASHES' array. 103 | * @access public 104 | */ 105 | public function getHash($k) { 106 | $array = $this->URL_HASHES; 107 | return $array[$k]; 108 | } 109 | /** getHash - Get the object's 'URL_HASHES' array. 110 | * @access public 111 | * @return Array returns array of hash-key-pairs for the object url. 112 | */ 113 | public function getHashes() { 114 | return $this->URL_HASHES; 115 | } 116 | 117 | /** getQueryStrings - Get an array of formatted request query strings. 118 | * @access public 119 | */ 120 | public function getQueryStrings() { 121 | return $this->GTB_QUERY_STRINGS; 122 | } 123 | 124 | /** getQueryUrls - Get the object's 'URL_HASHES' array. 125 | * @access public 126 | * @return Array returns array of all possible url combinations. 127 | */ 128 | public function getQueryUrls($limit=NULL) { 129 | $a = self::getQueryUrl(); 130 | $b = self::getHashes(); 131 | $QueryUrls = array(); 132 | $limit = (NULL!==$limit && is_numeric($limit)) ? (int)$limit : 0; 133 | $c = 0; 134 | //Foreach hash key value... 135 | foreach ( $b as $k => $v ) { 136 | //...that is a string with length > 0... 137 | if ( is_string($v) AND strlen($v) > 0 ) { 138 | //...format a query string. 139 | $rs = sprintf(tbr::QUERY_STRING, $v, $a); 140 | //Then, foreach available toolbar hostname... 141 | foreach ( $this->GTB_SERVER['host'] as $host ) { 142 | //...append any available top level domain... 143 | foreach ($this->GTB_SERVER['tld'] as $tld) { 144 | $tbUri = 'http://'. $host . $tld . tbr::SERVER_PATH . $rs; 145 | if ( $c < $limit || $limit == 0 ) { 146 | $QueryUrls[] = $tbUri; 147 | } 148 | $c++; 149 | } 150 | } 151 | } 152 | } 153 | return (sizeof($QueryUrls)>0) ? $QueryUrls : FALSE; 154 | } 155 | 156 | /** getTbrServer - Get the Google Toolbar server vars array. 157 | * @access public 158 | * @return Array Array contains keys: 'host', 'tld', 'path'. 159 | */ 160 | public function getTbrServer() { 161 | return $this->GTB_SERVER; 162 | } 163 | /** getTbrHosts - Get all available host names. 164 | * @access public 165 | * @return Array Array containing all available Toolbar server host names. 166 | */ 167 | public function getTbrHosts() { 168 | return $this->GTB_SERVER['host']; 169 | } 170 | /** getTbrTlds - Get all available top level domains. 171 | * @access public 172 | * @return Array Array containing all available Toolbar server top level domains. 173 | */ 174 | public function getTbrTlds() { 175 | return $this->GTB_SERVER['tld']; 176 | } 177 | /** getTbrTldSuggestion - Get Google's suggestion which top level domain to use. 178 | * @access public 179 | * @return Array Array containing all available Toolbar server top level domains. 180 | */ 181 | public function getTbrTldSuggestion() { 182 | $tmp = explode(".google.", GTB_Request::_get(tbr::SUGGEST_TLD_URL)); 183 | return isset($tmp[1]) ? trim($tmp[1]) : 'com'; 184 | } 185 | /** getTbrPath - Get the Google Toolbar Pagerank request path. 186 | * @access public 187 | * @return String 188 | */ 189 | public function getTbrPath() { 190 | return $this->GTB_SERVER['path']; 191 | } 192 | 193 | public function getPref($k) { 194 | if ($k == 'tld') { 195 | return pref::PREFERED_TLD; 196 | } 197 | } 198 | 199 | public function GPR_awesomeHash() { 200 | $a = self::getQueryURL(); 201 | if (NULL!==$a) { 202 | return GTB_awesomeHash::awesomeHash($a); } 203 | else { GTB_Exception::noUrl(); } 204 | } 205 | public function GPR_jenkinsHash() { 206 | $a = self::getQueryURL(); 207 | if (NULL!==$a) { 208 | return GTB_jenkinsHash::jenkinsHash($a); } 209 | else { GTB_Exception::noUrl(); } 210 | } 211 | public function GPR_jenkinsHash2() { 212 | $a = self::getQueryURL(); 213 | if (NULL!==$a) { 214 | return GTB_jenkinsHash::jenkinsHash2($a); } 215 | else { GTB_Exception::noUrl(); } 216 | } 217 | public function GPR_ieHash() { 218 | $a = self::getQueryURL(); 219 | if (NULL!==$a) { 220 | return GTB_ieHash::ieHash($a); } 221 | else { GTB_Exception::noUrl(); } 222 | } 223 | 224 | // setQueryURL setter function for the url key. 225 | // @return Boolean returns true if input string validated as url, else false. 226 | private function setQueryURL($a) { 227 | $this->QUERY_URL = $a; 228 | $b = array( 229 | 'jenkins' => self::GPR_jenkinsHash(), 230 | 'jenkins2'=> self::GPR_jenkinsHash2(), 231 | 'ie' => self::GPR_ieHash(), 232 | 'awesome' => self::GPR_awesomeHash() ); 233 | $this->URL_HASHES = $b; 234 | return (bool) self::setQueryStrings($a, $b); 235 | } 236 | private function setQueryStrings($a,$b) { 237 | $qs = array(); 238 | foreach ($b as $k => $v) { //Foreach hash key value... 239 | if(is_string($v) && strlen($v) > 0) { 240 | //...format a query string. 241 | $qs[] = sprintf(tbr::QUERY_STRING, $v, urlencode($a)); 242 | } 243 | } 244 | if (sizeof($qs) > 0) { 245 | $this->GTB_QUERY_STRINGS = $qs; 246 | return TRUE; 247 | } 248 | return FALSE; 249 | } 250 | }//eoc 251 | 252 | /** GTB_awesomeHash Hash a variable-length key into a 32-bit value. 253 | * @package GTB_PageRank 254 | * @author Stephan Schmitz 255 | */ 256 | class GTB_awesomeHash extends GTB_PageRank 257 | { 258 | // hash seed, used by the "awesomeHash" algrorithm 259 | const HASH_SEED = "Mining PageRank is AGAINST GOOGLE'S TERMS OF SERVICE. Yes, I'm talking to you, scammer."; 260 | 261 | // awesomeHash - Validates input, pass it to the hash function and return the result. 262 | public static function awesomeHash($a) { 263 | return self::_awesomeHash($a); 264 | } 265 | // _awesomeHash - Returns the computed hash for string $a. 266 | public static function _awesomeHash($a) { 267 | $b = 16909125; for ($c=0; $c 277 | */ 278 | class GTB_jenkinsHash extends GTB_PageRank 279 | { 280 | // jenkinsHash Validates input, pass it to the hash function and return the result. 281 | public static function jenkinsHash($a) { 282 | $b = GTB_Helper::strOrds("info:".$a); 283 | return self::_jenkinsHash ($b); 284 | } 285 | // jenkinsHash2 Validates input, pass it to the hash function and return the result. 286 | public static function jenkinsHash2($a) { 287 | $ch = sprintf("%u", self::_jenkinsHash($a, FALSE)); 288 | $ch = ((GTB_Helper::leftShift32(($ch/7), 2)) | ((GTB_Helper::_fmod($ch, 13)) & 7)); 289 | $buf = array($ch); 290 | for($i=1; $i<20; $i++) { $buf[$i] = $buf[$i-1]-9; } 291 | return sprintf("6%u", self::_jenkinsHash(GTB_Helper::c32to8bit($buf), FALSE)); 292 | } 293 | // @copyright (c) 1996 Bob Jenkins 294 | // @see http://www.burtleburtle.net/bob/c/lookup2.c 295 | public static function _jenkinsHash($key, $encode=TRUE) { 296 | $url = $key; 297 | $length = sizeof($url); // the key's length 298 | $a = $b = 0x000000009E3779B9; // the golden ratio; an arbitrary value 299 | $c = 0x00000000E6359A60; // the previous hash, or an arbitrary value 300 | $k = 0; $len = $length; 301 | while($len >= 12) { // handle most of the key 302 | $a += $url[$k+0]; 303 | $a += GTB_Helper::leftShift32($url[$k+1], 8); 304 | $a += GTB_Helper::leftShift32($url[$k+2], 16); 305 | $a += GTB_Helper::leftShift32($url[$k+3], 24); 306 | $b += $url[$k+4]; 307 | $b += GTB_Helper::leftShift32($url[$k+5], 8); 308 | $b += GTB_Helper::leftShift32($url[$k+6], 16); 309 | $b += GTB_Helper::leftShift32($url[$k+7], 24); 310 | $c += $url[$k+8]; 311 | $c += GTB_Helper::leftShift32($url[$k+9], 8); 312 | $c += GTB_Helper::leftShift32($url[$k+10],16); 313 | $c += GTB_Helper::leftShift32($url[$k+11],24); 314 | $mix = self::hashmixJenkins2($a, $b, $c); 315 | $a = $mix[0]; $b = $mix[1]; $c = $mix[2]; 316 | $len -= 12; $k += 12; 317 | } $c += $length; // handle the last 11 bytes 318 | switch($len) { // all the case statements fall through 319 | case 11: $c += GTB_Helper::leftShift32($url[$k+10],24); 320 | case 10: $c += GTB_Helper::leftShift32($url[$k+9], 16); 321 | case 9 : $c += GTB_Helper::leftShift32($url[$k+8], 8); 322 | // the first byte of $c is reserved for the length 323 | case 8 : $b += GTB_Helper::leftShift32($url[$k+7], 24); 324 | case 7 : $b += GTB_Helper::leftShift32($url[$k+6], 16); 325 | case 6 : $b += GTB_Helper::leftShift32($url[$k+5], 8); 326 | case 5 : $b += $url[$k+4]; 327 | case 4 : $a += GTB_Helper::leftShift32($url[$k+3], 24); 328 | case 3 : $a += GTB_Helper::leftShift32($url[$k+2], 16); 329 | case 2 : $a += GTB_Helper::leftShift32($url[$k+1], 8); 330 | case 1 : $a += $url[$k+0]; 331 | // case 0: nothing left to add 332 | } 333 | $mix = self::hashmixJenkins2($a, $b, $c); 334 | $ch = GTB_Helper::mask32($mix[2]); 335 | $ch = ($encode!==TRUE) ? $ch : sprintf("6%u", $ch); 336 | return $ch; 337 | } 338 | // hashmixJenkins2 - Mix three 32-bit values reversibly. 339 | // (c) 1996 Bob Jenkins 340 | // @see http://www.burtleburtle.net/bob/c/lookup2.c 341 | private static function hashmixJenkins2($a, $b, $c) { 342 | $a -= $b; $a -= $c; $a ^= GTB_Helper::unsignedRightShift($c, 13); 343 | $b -= $c; $b -= $a; $b ^= GTB_Helper::leftShift32($a, 8); 344 | $c -= $a; $c -= $b; $c ^= GTB_Helper::unsignedRightShift(($b & 0x00000000FFFFFFFF), 13); 345 | $a -= $b; $a -= $c; $a ^= GTB_Helper::unsignedRightShift(($c & 0x00000000FFFFFFFF), 12); 346 | $b -= $c; $b -= $a; $b = ($b ^ (GTB_Helper::leftShift32($a, 16))) & 0x00000000FFFFFFFF; 347 | $c -= $a; $c -= $b; $c = ($c ^ (GTB_Helper::unsignedRightShift($b, 5))) & 0x00000000FFFFFFFF; 348 | $a -= $b; $a -= $c; $a = ($a ^ (GTB_Helper::unsignedRightShift($c, 3))) & 0x00000000FFFFFFFF; 349 | $b -= $c; $b -= $a; $b = ($b ^ (GTB_Helper::leftShift32($a, 10))) & 0x00000000FFFFFFFF; 350 | $c -= $a; $c -= $b; $c = ($c ^ (GTB_Helper::unsignedRightShift($b, 15))) & 0x00000000FFFFFFFF; 351 | return array($a, $b, $c); 352 | } 353 | }//eoc 354 | /** GTB_jenkinsHash Hash a variable-length key into a 32-bit value. 355 | * @package GTB_PageRank 356 | * @author Stephan Schmitz 357 | */ 358 | class GTB_ieHash extends GTB_PageRank 359 | { 360 | // ieHash - Validates input, pass it to the hash function and return the result. 361 | public static function ieHash ($a) { 362 | return self::_ieHash($a); 363 | } 364 | // _ieHash - Checksum algorithm used in the IE version of the Google Toolbar. 365 | public static function _ieHash ($a) { 366 | $NumHashString = sprintf('%u', self::hashmixIE($a)); 367 | $NumHashLength = strlen($NumHashString); 368 | $CheckByte = 0; 369 | for ($i=($NumHashLength-1); $i>=0; $i--) { 370 | $Num = $NumHashString{$i}; 371 | $CheckByte += (1===($i % 2)) ? (int)((($Num*2)/10)+(($Num*2)%10)) : $Num; 372 | } $CheckByte %= 10; 373 | if ($CheckByte !== 0) { 374 | $CheckByte = 10-$CheckByte; 375 | if (($NumHashLength % 2) === 1) { 376 | if (($CheckByte % 2) === 1) { 377 | $CheckByte += 9; } 378 | $CheckByte >>= 1; } 379 | } 380 | return '7'.$CheckByte.$NumHashString; 381 | } 382 | // hashmixIE - Generates a hash for a url provided by msieHash. 383 | public static function hashmixIE ($url) { 384 | $c1 = GTB_Helper::strToNum($url, 0x1505, 0x21); 385 | $c2 = GTB_Helper::strToNum($url, 0, 0x1003f); 386 | $c1 = GTB_Helper::unsignedRightShift($c1, 2); 387 | $c1 = (GTB_Helper::unsignedRightShift($c1, 4) & 0x3ffffc0) | ($c1 & 0x3f); 388 | $c1 = (GTB_Helper::unsignedRightShift($c1, 4) & 0x3ffc00) | ($c1 & 0x3ff); 389 | $c1 = (GTB_Helper::unsignedRightShift($c1, 4) & 0x3c000) | ($c1 & 0x3fff); 390 | $t1 = (GTB_Helper::leftShift32( ( GTB_Helper::leftShift32( ($c1 & 0x3c0), 4) | ($c1 & 0x3c)), 2)) | ($c2 & 0xf0f); 391 | $t2 = (GTB_Helper::leftShift32( ( GTB_Helper::leftShift32( ($c1 & 0xffffc000), 4) | ($c1 & 0x3c00)), 0xa)) | ($c2 & 0xf0f0000); 392 | return GTB_Helper::mask32(($t1 | $t2)); 393 | } 394 | }//eoc 395 | 396 | /** GTB_Helper Various methods for bit and int operations. 397 | * @package GTB_PageRank 398 | * @author Stephan Schmitz 399 | */ 400 | class GTB_Helper extends GTB_PageRank 401 | { 402 | // 64-bit safe, bit-wise left shift. By James Wade. 403 | public static function leftShift32($x, $y) { 404 | $n = $x << $y; 405 | if (PHP_INT_MAX != 0x80000000) { 406 | $n = -(~($n & 0x00000000FFFFFFFF) + 1); 407 | } return (int)$n; 408 | } 409 | // Unsigned right bit shift 410 | public static function unsignedRightShift($x, $y) { 411 | // convert to 32 bits 412 | if (0xffffffff < $x || -0xffffffff > $x) { 413 | $x = GTB_Helper::_fmod($x, 0xffffffff + 1); 414 | } // convert to unsigned integer 415 | if (0x7fffffff < $x) { 416 | $x -= 0xffffffff + 1.0; 417 | } elseif (-0x80000000 > $x) { 418 | $x += 0xffffffff + 1.0; 419 | } // do right shift 420 | if (0 > $x) { 421 | $x &= 0x7fffffff; # remove sign bit before shift 422 | $x >>= $y; # right shift 423 | $x |= 1 << (31 - $y); # set shifted sign bit 424 | } else { 425 | $x >>= $y; # use normal right shift 426 | } 427 | return (int)$x; 428 | } 429 | // mask32 - On 64-bit platforms, masks integer $a and complements bits. 430 | public static function mask32($a) { 431 | if (PHP_INT_MAX != 0x0000000080000000) { # 2147483647 432 | $a = -(~($a & 0x00000000FFFFFFFF) + 1); 433 | } return (int)$a; 434 | } 435 | // hexEncodeU32 - Returns the hexadecimal string representation for U32 integer $a. 436 | public static function hexEncodeU32 ($a) { 437 | $b = self::toHex8(self::unsignedRightShift($a, 24)); 438 | $b .= self::toHex8(self::unsignedRightShift($a, 16) & 255); 439 | $b .= self::toHex8(self::unsignedRightShift($a, 8) & 255); 440 | return $b . self::toHex8($a & 255); 441 | } 442 | // toHex8 - Returns the hexadecimal string representation for integer $a. 443 | public static function toHex8 ($a) { 444 | return ($a < 16 ? "0" : "") . dechex($a); 445 | } 446 | // Unicode/multibyte capable equivelant of ord(). 447 | public static function charCodeAt($a, $b) { 448 | $a = mb_convert_encoding($a,"UCS-4BE","UTF-8"); 449 | $c = unpack("N", mb_substr($a,$b,1,"UCS-4BE")); 450 | return $c[1]; 451 | } 452 | // Turns a string of unicode characters into an array of ordinal values. 453 | public static function strOrds($a) { 454 | $b = array(); 455 | $a = mb_convert_encoding($a,"UCS-4BE","UTF-8"); 456 | for ($i=0; $i= $int32unit) { 477 | $c = ($c - $int32unit * (int)($c / $int32unit)); 478 | // if $c is less than -2^31 479 | $c = ($c < 0x0000000080000000) ? ($c + $int32unit) : $c; 480 | } 481 | $c += GTB_Helper::charCodeAt($str, $i); 482 | } return $c; 483 | } 484 | public static function _fmod($x, $y) { 485 | $i = floor( $x / $y ); 486 | return (int)( $x - $i * $y ); 487 | } 488 | 489 | // array_rand_val - Returns $n random values from array $a. 490 | public static function array_rand_val($a, $n=1) { 491 | shuffle($a); 492 | $b = array(); 493 | for ($i=0; $i<$n; $i++) { 494 | $b[] = $a[$i]; } 495 | return $n==1 ? $b[0] : $b; 496 | } 497 | // array_rand_val_assoc - Returns $n random values from assoc array $a. 498 | public static function array_rand_val_assoc($a, $n=1) { 499 | $k = array_keys($a); 500 | shuffle($k); 501 | $b = array(); 502 | for ($i=0; $i<$n; $i++) { 503 | $b[$k[$i]] = $a[$k[$i]]; } 504 | return $b; 505 | } 506 | 507 | // use regex to match values from string, if native json_decode is not available. 508 | public static function _json_decode($a) { 509 | if (TRUE !== function_exists('json_decode')) { 510 | $m = array(); 511 | preg_match_all('#"(.*?)"#si', $a, $m); 512 | return (isset($m[1]) && sizeof($m[1])>0) ? $m[1] : FALSE; 513 | } else { 514 | return json_decode($a); 515 | } 516 | } 517 | }//eoc 518 | /** GTB_Request Connection helper methods. 519 | * @package GTB_PageRank 520 | * @author Stephan Schmitz 521 | */ 522 | class GTB_Request extends GTB_PageRank 523 | { 524 | public static function _get($url) { 525 | if (!function_exists('curl_init')) { 526 | return self::GetWithoutCurl($url); } 527 | else { 528 | return self::GetWithCurl($url); } 529 | } 530 | /** 531 | * HTTP GET request with curl. 532 | * @access private 533 | * @param string $url String, containing the URL to curl. 534 | * @return string Returns string, containing the curl result. 535 | */ 536 | private static function GetWithCurl($url) { 537 | $ua = \SEOstats\Helper\HttpRequest::getUserAgent(); 538 | $curlopt_proxy = \SEOstats\Helper\HttpRequest::getProxy(); 539 | $curlopt_proxyuserpwd = \SEOstats\Helper\HttpRequest::getProxyUserPwd(); 540 | 541 | $ch = curl_init($url); 542 | curl_setopt($ch,CURLOPT_USERAGENT,$ua); 543 | if($curlopt_proxy) { 544 | curl_setopt($ch, CURLOPT_PROXY, $curlopt_proxy); 545 | } 546 | if($curlopt_proxyuserpwd) { 547 | curl_setopt($ch, CURLOPT_PROXYUSERPWD, $curlopt_proxyuserpwd); 548 | } 549 | curl_setopt($ch,CURLOPT_RETURNTRANSFER,1); 550 | curl_setopt($ch,CURLOPT_CONNECTTIMEOUT,5); 551 | curl_setopt($ch,CURLOPT_FOLLOWLOCATION,1); 552 | curl_setopt($ch,CURLOPT_MAXREDIRS,2); 553 | curl_setopt($ch,CURLOPT_SSL_VERIFYPEER,false); 554 | $str = curl_exec($ch); 555 | curl_close($ch); 556 | return $str; 557 | } 558 | } 559 | /** GTB_Exception GTB_PageRank exception class. 560 | * @package GTB_PageRank 561 | * @author Stephan Schmitz 562 | */ 563 | class GTB_Exception extends Exception 564 | { 565 | // exitNoUrl - throws an exception and exits, when trying to create a new object on no input. 566 | static function noUrl() { 567 | header("Content-Type: text/plain;"); 568 | throw new GTB_Exception("No Query URL defined! Use `new GTB_PageRank('http://www.domain.tld')` to create a new GTB_PageRank object."); 569 | exit(0); 570 | } 571 | static function tryAgain() { 572 | header("Content-Type: text/plain;"); 573 | throw new GTB_Exception("Error. Please try again!"); 574 | exit(0); 575 | } 576 | } 577 | 578 | /* DOCUMENTATION AND TEST PROGRAM - Run './GTB_PageRank.php?man' to view the content below! 579 | */ 580 | function print_ln() { 581 | print "--------------------------------------------------------------------------------------------------------\n"; } 582 | function print_cbb($a="") { 583 | if($a!="") { print_n("\nBelow, see the output of `var_dump( $a );` :"); } 584 | print "------------------------------------------------------------------------------------- CODEBLOCK BEGIN --\n"; } 585 | function print_cbe() { 586 | print "--------------------------------------------------------------------------------------- CODEBLOCK END --\n"; } 587 | function print_n($a="") { 588 | print "$a\n"; } 589 | function print_h($a) { 590 | print_n(""); print_n($a); print_ln(); } 591 | if ( TRUE !== DISABLE_MAN AND isset($_GET['man']) ) : 592 | try { 593 | //init a test object 594 | $url = (isset($_GET['url']) && !empty($_GET['url'])) ? $_GET['url'] : 'http://www.nahklick.de'; 595 | $_url = new GTB_PageRank($url); 596 | //send docs 597 | if ( !headers_sent() ): 598 | header("Content-Type: text/plain;"); 599 | else : 600 | $codewrap = TRUE; 601 | print "
\n";
602 |   endif;
603 |   print_h("= PHP Class 'GTB_PageRank' Documentation and Test Program =\n(c) 2012 Stephan Schmitz ");
604 |   print_n("Note: To use the test program with another domain, run `./GTB_PageRank.php?man&url=http://domain.tld`.");
605 |   print_ln();
606 |     print_h("== OBJECT INITIALIZATION ==");
607 |     print_n("To lookup the Google (Toolbar) PageRank, create a new object of the class 'GTB_PageRank'.");
608 |     print_n("For this example, the object is assigned to the variable '\$_url'.");
609 |     print_cbb();							# BEGIN code block
610 |     print_n("include ('GTB_PageRank.php');");
611 |     print_n("try {");
612 |     print_n("  \$_url = new GTB_PageRank('$url');");
613 |     print_n("}");
614 |     print_n("catch (GTB_Exception \$e) {");
615 |     print_n("  die(\$e->getMessage());");
616 |     print_n("}");
617 |     print_cbe(); 							# END code block
618 |     print_h("== OBJECT MODEL ==");
619 |     print_n("Now first, let's have a look at the class object itself.");
620 |     print_n();
621 |     print_n("When creating an object, it holds already all the data we need to send requests to the Toolbar Url.");
622 |     print_n("It contains eight array keys. Below, see the simplified data object model:");
623 |     print_cbb();
624 |     print_n("object(GTB_PageRank)#n (8) {");
625 |     print_n("  [\"QUERY_URL\"]  => string(n)                  # Url to get the PageRank for.");
626 |     print_n("  [\"URL_HASHES\"] => array(4) {                 # Array of computed hashes for url key (max. Arraysize: 4).");
627 |     print_n("    [\"awesome\"]  => string(9) \"8xxxxxxxx\"      #   9 chars, first is 8");
628 |     print_n("    [\"jenkins\"]  => string(n) \"6xxxxxxxxx\"     #   10-12 chars, first is 6");
629 |     print_n("    [\"jenkins2\"] => string(n) \"6xyxxxyxxy\"     #   10-12 chars, first is 6");
630 |     print_n("    [\"ie\"]       => string(n) \"7xxxxxxxxxx\"}   #   11-12 chars, first is 7");
631 |     print_n("  [\"PREFERED_TLD\"] => string(n)                # The Toolbar top level domain *you* prefer.");
632 |     print_n("  [\"GTB_SUGESSTED_TLD\"] => string(n)           # The Toolbar top level domain Google suggests to you.");
633 |     print_n("  [\"GTB_QUERY_STRINGS\"] => string(n)           # Array of possible path combination, based on different hashes (max. Arraysize: 4).");
634 |     print_n("  [\"GTB_SERVER\"] => array(3) {                 # Array containing the Toolbar server adress parts.");
635 |     print_n("    [\"host\"]  => array(2)                      #   Array containing valid toolbar host names.");
636 |     print_n("    [\"tld\"]  => array(138)                     #   Array containing valid toolbar top level domains.");
637 |     print_n("    [\"path\"] => string(4) \"/tbr\" }             #   The toolbar request path.");
638 |     print_n("}");
639 |     print_cbe();
640 |     print_n("For the initialized test URL, the full object looks as follows.");
641 |     print_cbb("\$_url"); 					# BEGIN code block
642 |     var_dump( $_url );
643 |     print_cbe(); 							# END code block
644 |     print_h("== GET THE PAGERANK ==");
645 |     print_n("As we saw, the object holds all data we need to request the PageRank.");
646 | 	print_n("Now, it is just a question of how you work with the data.");
647 | 	print_n();
648 | 	print_n("Need a single hash key? Try one of:");
649 |     print_cbb(); 							# BEGIN code block
650 |     print_n("\t`\$_url->GPR_ieHash()`\n\t`\$_url->GPR_jenkinsHash()`\n\t`\$_url->GPR_jenkinsHash2()` or \n\t`\$_url->GPR_awesomeHash()` :");
651 |     print_n("Output:");
652 | 	print_n("\t".$_url->GPR_ieHash()."\n\t".$_url->GPR_jenkinsHash()."\n\t".$_url->GPR_jenkinsHash2()."\n\t".$_url->GPR_awesomeHash() );
653 |     print_cbe(); 							# END code block
654 | 	print_n();
655 |     print_n("The same could be achieved using the `getHash(key)` method providing one of the key names");
656 |     print_n("'awesome', 'jenkins', 'jenkins2', or 'ie'.");
657 |     print_cbb("\$_url->getHash('awesome')");# BEGIN code block
658 |     var_dump( $_url->getHash('awesome') );
659 |     print_cbe(); 							# END code block
660 | 	print_n();
661 |     print_n("You want all-in-one? `\$_url->getHashes()` will give it to you!");
662 |     print_cbb("\$_url->getHashes()");# BEGIN code block
663 |     var_dump( $_url->getHashes() );
664 |     print_cbe(); 							# END code block
665 | 	print_n();
666 |     print_n("You want all-in-one? `\$_url->getHashes()` will give it to you!");
667 |     print_cbb("\$_url->getPageRank()");# BEGIN code block
668 |     var_dump( $_url->getQueryUrls() );
669 |     print_cbe(); 							# END code block
670 |   if( isset($codewrap) ) {
671 |     print "
"; 672 | } 673 | } catch (GTB_Exception $e) { 674 | die($e->getMessage()); 675 | } 676 | endif;//eof 677 | -------------------------------------------------------------------------------- /SEOstats/Services/3rdparty/JSON.php: -------------------------------------------------------------------------------- 1 | 51 | * @author Matt Knapp 52 | * @author Brett Stimmerman 53 | * @copyright 2005 Michal Migurski 54 | * @version CVS: $Id: JSON.php,v 1.31 2006/06/28 05:54:17 migurski Exp $ 55 | * @license http://www.opensource.org/licenses/bsd-license.php 56 | * @link http://pear.php.net/pepr/pepr-proposal-show.php?id=198 57 | */ 58 | 59 | /** 60 | * Marker constant for Services_JSON::decode(), used to flag stack state 61 | */ 62 | define('SERVICES_JSON_SLICE', 1); 63 | 64 | /** 65 | * Marker constant for Services_JSON::decode(), used to flag stack state 66 | */ 67 | define('SERVICES_JSON_IN_STR', 2); 68 | 69 | /** 70 | * Marker constant for Services_JSON::decode(), used to flag stack state 71 | */ 72 | define('SERVICES_JSON_IN_ARR', 3); 73 | 74 | /** 75 | * Marker constant for Services_JSON::decode(), used to flag stack state 76 | */ 77 | define('SERVICES_JSON_IN_OBJ', 4); 78 | 79 | /** 80 | * Marker constant for Services_JSON::decode(), used to flag stack state 81 | */ 82 | define('SERVICES_JSON_IN_CMT', 5); 83 | 84 | /** 85 | * Behavior switch for Services_JSON::decode() 86 | */ 87 | define('SERVICES_JSON_LOOSE_TYPE', 16); 88 | 89 | /** 90 | * Behavior switch for Services_JSON::decode() 91 | */ 92 | define('SERVICES_JSON_SUPPRESS_ERRORS', 32); 93 | 94 | /** 95 | * Converts to and from JSON format. 96 | * 97 | * Brief example of use: 98 | * 99 | * 100 | * // create a new instance of Services_JSON 101 | * $json = new Services_JSON(); 102 | * 103 | * // convert a complexe value to JSON notation, and send it to the browser 104 | * $value = array('foo', 'bar', array(1, 2, 'baz'), array(3, array(4))); 105 | * $output = $json->encode($value); 106 | * 107 | * print($output); 108 | * // prints: ["foo","bar",[1,2,"baz"],[3,[4]]] 109 | * 110 | * // accept incoming POST data, assumed to be in JSON notation 111 | * $input = file_get_contents('php://input', 1000000); 112 | * $value = $json->decode($input); 113 | * 114 | */ 115 | class Services_JSON 116 | { 117 | /** 118 | * constructs a new JSON instance 119 | * 120 | * @param int $use object behavior flags; combine with boolean-OR 121 | * 122 | * possible values: 123 | * - SERVICES_JSON_LOOSE_TYPE: loose typing. 124 | * "{...}" syntax creates associative arrays 125 | * instead of objects in decode(). 126 | * - SERVICES_JSON_SUPPRESS_ERRORS: error suppression. 127 | * Values which can't be encoded (e.g. resources) 128 | * appear as NULL instead of throwing errors. 129 | * By default, a deeply-nested resource will 130 | * bubble up with an error, so all return values 131 | * from encode() should be checked with isError() 132 | */ 133 | function Services_JSON($use = 0) 134 | { 135 | $this->use = $use; 136 | } 137 | 138 | /** 139 | * convert a string from one UTF-16 char to one UTF-8 char 140 | * 141 | * Normally should be handled by mb_convert_encoding, but 142 | * provides a slower PHP-only method for installations 143 | * that lack the multibye string extension. 144 | * 145 | * @param string $utf16 UTF-16 character 146 | * @return string UTF-8 character 147 | * @access private 148 | */ 149 | function utf162utf8($utf16) 150 | { 151 | // oh please oh please oh please oh please oh please 152 | if(function_exists('mb_convert_encoding')) { 153 | return mb_convert_encoding($utf16, 'UTF-8', 'UTF-16'); 154 | } 155 | 156 | $bytes = (ord($utf16{0}) << 8) | ord($utf16{1}); 157 | 158 | switch(true) { 159 | case ((0x7F & $bytes) == $bytes): 160 | // this case should never be reached, because we are in ASCII range 161 | // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 162 | return chr(0x7F & $bytes); 163 | 164 | case (0x07FF & $bytes) == $bytes: 165 | // return a 2-byte UTF-8 character 166 | // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 167 | return chr(0xC0 | (($bytes >> 6) & 0x1F)) 168 | . chr(0x80 | ($bytes & 0x3F)); 169 | 170 | case (0xFFFF & $bytes) == $bytes: 171 | // return a 3-byte UTF-8 character 172 | // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 173 | return chr(0xE0 | (($bytes >> 12) & 0x0F)) 174 | . chr(0x80 | (($bytes >> 6) & 0x3F)) 175 | . chr(0x80 | ($bytes & 0x3F)); 176 | } 177 | 178 | // ignoring UTF-32 for now, sorry 179 | return ''; 180 | } 181 | 182 | /** 183 | * convert a string from one UTF-8 char to one UTF-16 char 184 | * 185 | * Normally should be handled by mb_convert_encoding, but 186 | * provides a slower PHP-only method for installations 187 | * that lack the multibye string extension. 188 | * 189 | * @param string $utf8 UTF-8 character 190 | * @return string UTF-16 character 191 | * @access private 192 | */ 193 | function utf82utf16($utf8) 194 | { 195 | // oh please oh please oh please oh please oh please 196 | if(function_exists('mb_convert_encoding')) { 197 | return mb_convert_encoding($utf8, 'UTF-16', 'UTF-8'); 198 | } 199 | 200 | switch(strlen($utf8)) { 201 | case 1: 202 | // this case should never be reached, because we are in ASCII range 203 | // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 204 | return $utf8; 205 | 206 | case 2: 207 | // return a UTF-16 character from a 2-byte UTF-8 char 208 | // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 209 | return chr(0x07 & (ord($utf8{0}) >> 2)) 210 | . chr((0xC0 & (ord($utf8{0}) << 6)) 211 | | (0x3F & ord($utf8{1}))); 212 | 213 | case 3: 214 | // return a UTF-16 character from a 3-byte UTF-8 char 215 | // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 216 | return chr((0xF0 & (ord($utf8{0}) << 4)) 217 | | (0x0F & (ord($utf8{1}) >> 2))) 218 | . chr((0xC0 & (ord($utf8{1}) << 6)) 219 | | (0x7F & ord($utf8{2}))); 220 | } 221 | 222 | // ignoring UTF-32 for now, sorry 223 | return ''; 224 | } 225 | 226 | /** 227 | * encodes an arbitrary variable into JSON format 228 | * 229 | * @param mixed $var any number, boolean, string, array, or object to be encoded. 230 | * see argument 1 to Services_JSON() above for array-parsing behavior. 231 | * if var is a strng, note that encode() always expects it 232 | * to be in ASCII or UTF-8 format! 233 | * 234 | * @return mixed JSON string representation of input var or an error if a problem occurs 235 | * @access public 236 | */ 237 | function encode($var) 238 | { 239 | switch (gettype($var)) { 240 | case 'boolean': 241 | return $var ? 'true' : 'false'; 242 | 243 | case 'NULL': 244 | return 'null'; 245 | 246 | case 'integer': 247 | return (int) $var; 248 | 249 | case 'double': 250 | case 'float': 251 | return (float) $var; 252 | 253 | case 'string': 254 | // STRINGS ARE EXPECTED TO BE IN ASCII OR UTF-8 FORMAT 255 | $ascii = ''; 256 | $strlen_var = strlen($var); 257 | 258 | /* 259 | * Iterate over every character in the string, 260 | * escaping with a slash or encoding to UTF-8 where necessary 261 | */ 262 | for ($c = 0; $c < $strlen_var; ++$c) { 263 | 264 | $ord_var_c = ord($var{$c}); 265 | 266 | switch (true) { 267 | case $ord_var_c == 0x08: 268 | $ascii .= '\b'; 269 | break; 270 | case $ord_var_c == 0x09: 271 | $ascii .= '\t'; 272 | break; 273 | case $ord_var_c == 0x0A: 274 | $ascii .= '\n'; 275 | break; 276 | case $ord_var_c == 0x0C: 277 | $ascii .= '\f'; 278 | break; 279 | case $ord_var_c == 0x0D: 280 | $ascii .= '\r'; 281 | break; 282 | 283 | case $ord_var_c == 0x22: 284 | case $ord_var_c == 0x2F: 285 | case $ord_var_c == 0x5C: 286 | // double quote, slash, slosh 287 | $ascii .= '\\'.$var{$c}; 288 | break; 289 | 290 | case (($ord_var_c >= 0x20) && ($ord_var_c <= 0x7F)): 291 | // characters U-00000000 - U-0000007F (same as ASCII) 292 | $ascii .= $var{$c}; 293 | break; 294 | 295 | case (($ord_var_c & 0xE0) == 0xC0): 296 | // characters U-00000080 - U-000007FF, mask 110XXXXX 297 | // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 298 | $char = pack('C*', $ord_var_c, ord($var{$c + 1})); 299 | $c += 1; 300 | $utf16 = $this->utf82utf16($char); 301 | $ascii .= sprintf('\u%04s', bin2hex($utf16)); 302 | break; 303 | 304 | case (($ord_var_c & 0xF0) == 0xE0): 305 | // characters U-00000800 - U-0000FFFF, mask 1110XXXX 306 | // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 307 | $char = pack('C*', $ord_var_c, 308 | ord($var{$c + 1}), 309 | ord($var{$c + 2})); 310 | $c += 2; 311 | $utf16 = $this->utf82utf16($char); 312 | $ascii .= sprintf('\u%04s', bin2hex($utf16)); 313 | break; 314 | 315 | case (($ord_var_c & 0xF8) == 0xF0): 316 | // characters U-00010000 - U-001FFFFF, mask 11110XXX 317 | // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 318 | $char = pack('C*', $ord_var_c, 319 | ord($var{$c + 1}), 320 | ord($var{$c + 2}), 321 | ord($var{$c + 3})); 322 | $c += 3; 323 | $utf16 = $this->utf82utf16($char); 324 | $ascii .= sprintf('\u%04s', bin2hex($utf16)); 325 | break; 326 | 327 | case (($ord_var_c & 0xFC) == 0xF8): 328 | // characters U-00200000 - U-03FFFFFF, mask 111110XX 329 | // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 330 | $char = pack('C*', $ord_var_c, 331 | ord($var{$c + 1}), 332 | ord($var{$c + 2}), 333 | ord($var{$c + 3}), 334 | ord($var{$c + 4})); 335 | $c += 4; 336 | $utf16 = $this->utf82utf16($char); 337 | $ascii .= sprintf('\u%04s', bin2hex($utf16)); 338 | break; 339 | 340 | case (($ord_var_c & 0xFE) == 0xFC): 341 | // characters U-04000000 - U-7FFFFFFF, mask 1111110X 342 | // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 343 | $char = pack('C*', $ord_var_c, 344 | ord($var{$c + 1}), 345 | ord($var{$c + 2}), 346 | ord($var{$c + 3}), 347 | ord($var{$c + 4}), 348 | ord($var{$c + 5})); 349 | $c += 5; 350 | $utf16 = $this->utf82utf16($char); 351 | $ascii .= sprintf('\u%04s', bin2hex($utf16)); 352 | break; 353 | } 354 | } 355 | 356 | return '"'.$ascii.'"'; 357 | 358 | case 'array': 359 | /* 360 | * As per JSON spec if any array key is not an integer 361 | * we must treat the the whole array as an object. We 362 | * also try to catch a sparsely populated associative 363 | * array with numeric keys here because some JS engines 364 | * will create an array with empty indexes up to 365 | * max_index which can cause memory issues and because 366 | * the keys, which may be relevant, will be remapped 367 | * otherwise. 368 | * 369 | * As per the ECMA and JSON specification an object may 370 | * have any string as a property. Unfortunately due to 371 | * a hole in the ECMA specification if the key is a 372 | * ECMA reserved word or starts with a digit the 373 | * parameter is only accessible using ECMAScript's 374 | * bracket notation. 375 | */ 376 | 377 | // treat as a JSON object 378 | if (is_array($var) && count($var) && (array_keys($var) !== range(0, sizeof($var) - 1))) { 379 | $properties = array_map(array($this, 'name_value'), 380 | array_keys($var), 381 | array_values($var)); 382 | 383 | foreach($properties as $property) { 384 | if(Services_JSON::isError($property)) { 385 | return $property; 386 | } 387 | } 388 | 389 | return '{' . join(',', $properties) . '}'; 390 | } 391 | 392 | // treat it like a regular array 393 | $elements = array_map(array($this, 'encode'), $var); 394 | 395 | foreach($elements as $element) { 396 | if(Services_JSON::isError($element)) { 397 | return $element; 398 | } 399 | } 400 | 401 | return '[' . join(',', $elements) . ']'; 402 | 403 | case 'object': 404 | $vars = get_object_vars($var); 405 | 406 | $properties = array_map(array($this, 'name_value'), 407 | array_keys($vars), 408 | array_values($vars)); 409 | 410 | foreach($properties as $property) { 411 | if(Services_JSON::isError($property)) { 412 | return $property; 413 | } 414 | } 415 | 416 | return '{' . join(',', $properties) . '}'; 417 | 418 | default: 419 | return ($this->use & SERVICES_JSON_SUPPRESS_ERRORS) 420 | ? 'null' 421 | : new Services_JSON_Error(gettype($var)." can not be encoded as JSON string"); 422 | } 423 | } 424 | 425 | /** 426 | * array-walking function for use in generating JSON-formatted name-value pairs 427 | * 428 | * @param string $name name of key to use 429 | * @param mixed $value reference to an array element to be encoded 430 | * 431 | * @return string JSON-formatted name-value pair, like '"name":value' 432 | * @access private 433 | */ 434 | function name_value($name, $value) 435 | { 436 | $encoded_value = $this->encode($value); 437 | 438 | if(Services_JSON::isError($encoded_value)) { 439 | return $encoded_value; 440 | } 441 | 442 | return $this->encode(strval($name)) . ':' . $encoded_value; 443 | } 444 | 445 | /** 446 | * reduce a string by removing leading and trailing comments and whitespace 447 | * 448 | * @param $str string string value to strip of comments and whitespace 449 | * 450 | * @return string string value stripped of comments and whitespace 451 | * @access private 452 | */ 453 | function reduce_string($str) 454 | { 455 | $str = preg_replace(array( 456 | 457 | // eliminate single line comments in '// ...' form 458 | '#^\s*//(.+)$#m', 459 | 460 | // eliminate multi-line comments in '/* ... */' form, at start of string 461 | '#^\s*/\*(.+)\*/#Us', 462 | 463 | // eliminate multi-line comments in '/* ... */' form, at end of string 464 | '#/\*(.+)\*/\s*$#Us' 465 | 466 | ), '', $str); 467 | 468 | // eliminate extraneous space 469 | return trim($str); 470 | } 471 | 472 | /** 473 | * decodes a JSON string into appropriate variable 474 | * 475 | * @param string $str JSON-formatted string 476 | * 477 | * @return mixed number, boolean, string, array, or object 478 | * corresponding to given JSON input string. 479 | * See argument 1 to Services_JSON() above for object-output behavior. 480 | * Note that decode() always returns strings 481 | * in ASCII or UTF-8 format! 482 | * @access public 483 | */ 484 | function decode($str) 485 | { 486 | $str = $this->reduce_string($str); 487 | 488 | switch (strtolower($str)) { 489 | case 'true': 490 | return true; 491 | 492 | case 'false': 493 | return false; 494 | 495 | case 'null': 496 | return null; 497 | 498 | default: 499 | $m = array(); 500 | 501 | if (is_numeric($str)) { 502 | // Lookie-loo, it's a number 503 | 504 | // This would work on its own, but I'm trying to be 505 | // good about returning integers where appropriate: 506 | // return (float)$str; 507 | 508 | // Return float or int, as appropriate 509 | return ((float)$str == (integer)$str) 510 | ? (integer)$str 511 | : (float)$str; 512 | 513 | } elseif (preg_match('/^("|\').*(\1)$/s', $str, $m) && $m[1] == $m[2]) { 514 | // STRINGS RETURNED IN UTF-8 FORMAT 515 | $delim = substr($str, 0, 1); 516 | $chrs = substr($str, 1, -1); 517 | $utf8 = ''; 518 | $strlen_chrs = strlen($chrs); 519 | 520 | for ($c = 0; $c < $strlen_chrs; ++$c) { 521 | 522 | $substr_chrs_c_2 = substr($chrs, $c, 2); 523 | $ord_chrs_c = ord($chrs{$c}); 524 | 525 | switch (true) { 526 | case $substr_chrs_c_2 == '\b': 527 | $utf8 .= chr(0x08); 528 | ++$c; 529 | break; 530 | case $substr_chrs_c_2 == '\t': 531 | $utf8 .= chr(0x09); 532 | ++$c; 533 | break; 534 | case $substr_chrs_c_2 == '\n': 535 | $utf8 .= chr(0x0A); 536 | ++$c; 537 | break; 538 | case $substr_chrs_c_2 == '\f': 539 | $utf8 .= chr(0x0C); 540 | ++$c; 541 | break; 542 | case $substr_chrs_c_2 == '\r': 543 | $utf8 .= chr(0x0D); 544 | ++$c; 545 | break; 546 | 547 | case $substr_chrs_c_2 == '\\"': 548 | case $substr_chrs_c_2 == '\\\'': 549 | case $substr_chrs_c_2 == '\\\\': 550 | case $substr_chrs_c_2 == '\\/': 551 | if (($delim == '"' && $substr_chrs_c_2 != '\\\'') || 552 | ($delim == "'" && $substr_chrs_c_2 != '\\"')) { 553 | $utf8 .= $chrs{++$c}; 554 | } 555 | break; 556 | 557 | case preg_match('/\\\u[0-9A-F]{4}/i', substr($chrs, $c, 6)): 558 | // single, escaped unicode character 559 | $utf16 = chr(hexdec(substr($chrs, ($c + 2), 2))) 560 | . chr(hexdec(substr($chrs, ($c + 4), 2))); 561 | $utf8 .= $this->utf162utf8($utf16); 562 | $c += 5; 563 | break; 564 | 565 | case ($ord_chrs_c >= 0x20) && ($ord_chrs_c <= 0x7F): 566 | $utf8 .= $chrs{$c}; 567 | break; 568 | 569 | case ($ord_chrs_c & 0xE0) == 0xC0: 570 | // characters U-00000080 - U-000007FF, mask 110XXXXX 571 | //see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 572 | $utf8 .= substr($chrs, $c, 2); 573 | ++$c; 574 | break; 575 | 576 | case ($ord_chrs_c & 0xF0) == 0xE0: 577 | // characters U-00000800 - U-0000FFFF, mask 1110XXXX 578 | // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 579 | $utf8 .= substr($chrs, $c, 3); 580 | $c += 2; 581 | break; 582 | 583 | case ($ord_chrs_c & 0xF8) == 0xF0: 584 | // characters U-00010000 - U-001FFFFF, mask 11110XXX 585 | // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 586 | $utf8 .= substr($chrs, $c, 4); 587 | $c += 3; 588 | break; 589 | 590 | case ($ord_chrs_c & 0xFC) == 0xF8: 591 | // characters U-00200000 - U-03FFFFFF, mask 111110XX 592 | // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 593 | $utf8 .= substr($chrs, $c, 5); 594 | $c += 4; 595 | break; 596 | 597 | case ($ord_chrs_c & 0xFE) == 0xFC: 598 | // characters U-04000000 - U-7FFFFFFF, mask 1111110X 599 | // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 600 | $utf8 .= substr($chrs, $c, 6); 601 | $c += 5; 602 | break; 603 | 604 | } 605 | 606 | } 607 | 608 | return $utf8; 609 | 610 | } elseif (preg_match('/^\[.*\]$/s', $str) || preg_match('/^\{.*\}$/s', $str)) { 611 | // array, or object notation 612 | 613 | if ($str{0} == '[') { 614 | $stk = array(SERVICES_JSON_IN_ARR); 615 | $arr = array(); 616 | } else { 617 | if ($this->use & SERVICES_JSON_LOOSE_TYPE) { 618 | $stk = array(SERVICES_JSON_IN_OBJ); 619 | $obj = array(); 620 | } else { 621 | $stk = array(SERVICES_JSON_IN_OBJ); 622 | $obj = new stdClass(); 623 | } 624 | } 625 | 626 | array_push($stk, array('what' => SERVICES_JSON_SLICE, 627 | 'where' => 0, 628 | 'delim' => false)); 629 | 630 | $chrs = substr($str, 1, -1); 631 | $chrs = $this->reduce_string($chrs); 632 | 633 | if ($chrs == '') { 634 | if (reset($stk) == SERVICES_JSON_IN_ARR) { 635 | return $arr; 636 | 637 | } else { 638 | return $obj; 639 | 640 | } 641 | } 642 | 643 | //print("\nparsing {$chrs}\n"); 644 | 645 | $strlen_chrs = strlen($chrs); 646 | 647 | for ($c = 0; $c <= $strlen_chrs; ++$c) { 648 | 649 | $top = end($stk); 650 | $substr_chrs_c_2 = substr($chrs, $c, 2); 651 | 652 | if (($c == $strlen_chrs) || (($chrs{$c} == ',') && ($top['what'] == SERVICES_JSON_SLICE))) { 653 | // found a comma that is not inside a string, array, etc., 654 | // OR we've reached the end of the character list 655 | $slice = substr($chrs, $top['where'], ($c - $top['where'])); 656 | array_push($stk, array('what' => SERVICES_JSON_SLICE, 'where' => ($c + 1), 'delim' => false)); 657 | //print("Found split at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n"); 658 | 659 | if (reset($stk) == SERVICES_JSON_IN_ARR) { 660 | // we are in an array, so just push an element onto the stack 661 | array_push($arr, $this->decode($slice)); 662 | 663 | } elseif (reset($stk) == SERVICES_JSON_IN_OBJ) { 664 | // we are in an object, so figure 665 | // out the property name and set an 666 | // element in an associative array, 667 | // for now 668 | $parts = array(); 669 | 670 | if (preg_match('/^\s*(["\'].*[^\\\]["\'])\s*:\s*(\S.*),?$/Uis', $slice, $parts)) { 671 | // "name":value pair 672 | $key = $this->decode($parts[1]); 673 | $val = $this->decode($parts[2]); 674 | 675 | if ($this->use & SERVICES_JSON_LOOSE_TYPE) { 676 | $obj[$key] = $val; 677 | } else { 678 | $obj->$key = $val; 679 | } 680 | } elseif (preg_match('/^\s*(\w+)\s*:\s*(\S.*),?$/Uis', $slice, $parts)) { 681 | // name:value pair, where name is unquoted 682 | $key = $parts[1]; 683 | $val = $this->decode($parts[2]); 684 | 685 | if ($this->use & SERVICES_JSON_LOOSE_TYPE) { 686 | $obj[$key] = $val; 687 | } else { 688 | $obj->$key = $val; 689 | } 690 | } 691 | 692 | } 693 | 694 | } elseif ((($chrs{$c} == '"') || ($chrs{$c} == "'")) && ($top['what'] != SERVICES_JSON_IN_STR)) { 695 | // found a quote, and we are not inside a string 696 | array_push($stk, array('what' => SERVICES_JSON_IN_STR, 'where' => $c, 'delim' => $chrs{$c})); 697 | //print("Found start of string at {$c}\n"); 698 | 699 | } elseif (($chrs{$c} == $top['delim']) && 700 | ($top['what'] == SERVICES_JSON_IN_STR) && 701 | ((strlen(substr($chrs, 0, $c)) - strlen(rtrim(substr($chrs, 0, $c), '\\'))) % 2 != 1)) { 702 | // found a quote, we're in a string, and it's not escaped 703 | // we know that it's not escaped becase there is _not_ an 704 | // odd number of backslashes at the end of the string so far 705 | array_pop($stk); 706 | //print("Found end of string at {$c}: ".substr($chrs, $top['where'], (1 + 1 + $c - $top['where']))."\n"); 707 | 708 | } elseif (($chrs{$c} == '[') && 709 | in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) { 710 | // found a left-bracket, and we are in an array, object, or slice 711 | array_push($stk, array('what' => SERVICES_JSON_IN_ARR, 'where' => $c, 'delim' => false)); 712 | //print("Found start of array at {$c}\n"); 713 | 714 | } elseif (($chrs{$c} == ']') && ($top['what'] == SERVICES_JSON_IN_ARR)) { 715 | // found a right-bracket, and we're in an array 716 | array_pop($stk); 717 | //print("Found end of array at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n"); 718 | 719 | } elseif (($chrs{$c} == '{') && 720 | in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) { 721 | // found a left-brace, and we are in an array, object, or slice 722 | array_push($stk, array('what' => SERVICES_JSON_IN_OBJ, 'where' => $c, 'delim' => false)); 723 | //print("Found start of object at {$c}\n"); 724 | 725 | } elseif (($chrs{$c} == '}') && ($top['what'] == SERVICES_JSON_IN_OBJ)) { 726 | // found a right-brace, and we're in an object 727 | array_pop($stk); 728 | //print("Found end of object at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n"); 729 | 730 | } elseif (($substr_chrs_c_2 == '/*') && 731 | in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) { 732 | // found a comment start, and we are in an array, object, or slice 733 | array_push($stk, array('what' => SERVICES_JSON_IN_CMT, 'where' => $c, 'delim' => false)); 734 | $c++; 735 | //print("Found start of comment at {$c}\n"); 736 | 737 | } elseif (($substr_chrs_c_2 == '*/') && ($top['what'] == SERVICES_JSON_IN_CMT)) { 738 | // found a comment end, and we're in one now 739 | array_pop($stk); 740 | $c++; 741 | 742 | for ($i = $top['where']; $i <= $c; ++$i) 743 | $chrs = substr_replace($chrs, ' ', $i, 1); 744 | 745 | //print("Found end of comment at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n"); 746 | 747 | } 748 | 749 | } 750 | 751 | if (reset($stk) == SERVICES_JSON_IN_ARR) { 752 | return $arr; 753 | 754 | } elseif (reset($stk) == SERVICES_JSON_IN_OBJ) { 755 | return $obj; 756 | 757 | } 758 | 759 | } 760 | } 761 | } 762 | 763 | /** 764 | * @todo Ultimately, this should just call PEAR::isError() 765 | */ 766 | function isError($data, $code = null) 767 | { 768 | if (class_exists('pear')) { 769 | return PEAR::isError($data, $code); 770 | } elseif (is_object($data) && (get_class($data) == 'services_json_error' || 771 | is_subclass_of($data, 'services_json_error'))) { 772 | return true; 773 | } 774 | 775 | return false; 776 | } 777 | } 778 | 779 | if (class_exists('PEAR_Error')) { 780 | 781 | class Services_JSON_Error extends PEAR_Error 782 | { 783 | function Services_JSON_Error($message = 'unknown error', $code = null, 784 | $mode = null, $options = null, $userinfo = null) 785 | { 786 | parent::PEAR_Error($message, $code, $mode, $options, $userinfo); 787 | } 788 | } 789 | 790 | } else { 791 | 792 | /** 793 | * @todo Ultimately, this class shall be descended from PEAR_Error 794 | */ 795 | class Services_JSON_Error 796 | { 797 | function Services_JSON_Error($message = 'unknown error', $code = null, 798 | $mode = null, $options = null, $userinfo = null) 799 | { 800 | 801 | } 802 | } 803 | 804 | } 805 | 806 | ?> --------------------------------------------------------------------------------