├── 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('#
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 = '\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
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 | ?>
--------------------------------------------------------------------------------