Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/matomo-org/matomo.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMatthieu Aubry <matt@piwik.org>2015-02-12 23:44:44 +0300
committerMatthieu Aubry <matt@piwik.org>2015-02-12 23:44:44 +0300
commitc9770a9faa6c76e3d6c06584f3757bc43b140857 (patch)
tree6e87297ea77970bff426a233d62cc2bc7d941a8c /plugins
parent620c6f3da261995f3ea1062691c212f65137ee0a (diff)
parent7eb49e23c93c6e37ec73bee6f3ee48cfcdeea74c (diff)
Merge pull request #7174 from piwik/seo-metrics
Refactored the SEO plugin to make it extensible and error-proof
Diffstat (limited to 'plugins')
-rw-r--r--plugins/SEO/API.php117
-rw-r--r--plugins/SEO/Metric/Aggregator.php63
-rw-r--r--plugins/SEO/Metric/Alexa.php51
-rw-r--r--plugins/SEO/Metric/Bing.php54
-rw-r--r--plugins/SEO/Metric/Dmoz.php61
-rw-r--r--plugins/SEO/Metric/DomainAge.php141
-rw-r--r--plugins/SEO/Metric/Google.php170
-rw-r--r--plugins/SEO/Metric/Majestic.php (renamed from plugins/SEO/MajesticClient.php)48
-rw-r--r--plugins/SEO/Metric/Metric.php145
-rw-r--r--plugins/SEO/Metric/MetricsProvider.php21
-rw-r--r--plugins/SEO/Metric/ProviderCache.php48
-rw-r--r--plugins/SEO/RankChecker.php378
-rw-r--r--plugins/SEO/Widgets.php3
-rw-r--r--plugins/SEO/templates/getRank.twig15
14 files changed, 835 insertions, 480 deletions
diff --git a/plugins/SEO/API.php b/plugins/SEO/API.php
index c57aea2156..fdadbf518f 100644
--- a/plugins/SEO/API.php
+++ b/plugins/SEO/API.php
@@ -4,25 +4,28 @@
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
- *
*/
+
namespace Piwik\Plugins\SEO;
-use Piwik\Cache;
use Piwik\DataTable;
use Piwik\Piwik;
+use Piwik\Plugins\SEO\Metric\Aggregator;
+use Piwik\Plugins\SEO\Metric\Metric;
+use Piwik\Plugins\SEO\Metric\ProviderCache;
+use Piwik\Url;
/**
* @see plugins/Referrers/functions.php
- * @method static \Piwik\Plugins\SEO\API getInstance()
+ * @method static API getInstance()
*/
require_once PIWIK_INCLUDE_PATH . '/plugins/Referrers/functions.php';
/**
- * The SEO API lets you access a list of SEO metrics for the specified URL: Google Pagerank, Goolge/Bing indexed pages
+ * The SEO API lets you access a list of SEO metrics for the specified URL: Google PageRank, Google/Bing indexed pages
* Alexa Rank, age of the Domain name and count of DMOZ entries.
*
- * @method static \Piwik\Plugins\SEO\API getInstance()
+ * @method static API getInstance()
*/
class API extends \Piwik\Plugin\API
{
@@ -36,93 +39,37 @@ class API extends \Piwik\Plugin\API
{
Piwik::checkUserHasSomeViewAccess();
- $data = $this->getCachedRanks($url);
-
- $translated = array();
- if (!empty($data)) {
- foreach ($data as $title => $rank) {
- $translated[Piwik::translate($title)] = $rank;
- }
- }
+ $metricProvider = new ProviderCache(new Aggregator());
+ $domain = Url::getHostFromUrl($url);
+ $metrics = $metricProvider->getMetrics($domain);
- return DataTable::makeFromIndexedArray($translated);
+ return $this->toDataTable($metrics);
}
- private function getCachedRanks($url)
- {
- $cacheId = 'SEO_getRank_' . md5($url);
-
- $cache = Cache::getLazyCache();
- $data = $cache->fetch($cacheId);
-
- if (empty($data)) {
- $data = $this->getRanks($url);
-
- $cache->save($cacheId, $data, 60 * 60 * 6);
- }
-
- return $data;
- }
-
- private function getRanks($url)
+ /**
+ * @param Metric[] $metrics
+ * @return DataTable
+ */
+ private function toDataTable(array $metrics)
{
- $rank = new RankChecker($url);
-
- $linkToMajestic = MajesticClient::getLinkForUrl($url);
-
- $data = array(
- 'Google PageRank' => array(
- 'rank' => $rank->getPageRank(),
- 'logo' => \Piwik\Plugins\Referrers\getSearchEngineLogoFromUrl('http://google.com'),
- 'id' => 'pagerank'
- ),
- 'SEO_Google_IndexedPages' => array(
- 'rank' => $rank->getIndexedPagesGoogle(),
- 'logo' => \Piwik\Plugins\Referrers\getSearchEngineLogoFromUrl('http://google.com'),
- 'id' => 'google-index',
- ),
- 'SEO_Bing_IndexedPages' => array(
- 'rank' => $rank->getIndexedPagesBing(),
- 'logo' => \Piwik\Plugins\Referrers\getSearchEngineLogoFromUrl('http://bing.com'),
- 'id' => 'bing-index',
- ),
- 'SEO_AlexaRank' => array(
- 'rank' => $rank->getAlexaRank() . '',
- 'logo' => \Piwik\Plugins\Referrers\getSearchEngineLogoFromUrl('http://alexa.com'),
- 'id' => 'alexa',
- ),
- 'SEO_DomainAge' => array(
- 'rank' => $rank->getAge(),
- 'logo' => 'plugins/SEO/images/whois.png',
- 'id' => 'domain-age',
- ),
- 'SEO_ExternalBacklinks' => array(
- 'rank' => $rank->getExternalBacklinkCount(),
- 'logo' => 'plugins/SEO/images/majesticseo.png',
- 'logo_link' => $linkToMajestic,
- 'logo_tooltip' => Piwik::translate('SEO_ViewBacklinksOnMajesticSEO'),
- 'id' => 'external-backlinks',
- ),
- 'SEO_ReferrerDomains' => array(
- 'rank' => $rank->getReferrerDomainCount(),
- 'logo' => 'plugins/SEO/images/majesticseo.png',
- 'logo_link' => $linkToMajestic,
- 'logo_tooltip' => Piwik::translate('SEO_ViewBacklinksOnMajesticSEO'),
- 'id' => 'referrer-domains',
- ),
- );
+ $translated = array();
- // Add DMOZ only if > 0 entries found
- $dmozRank = array(
- 'rank' => $rank->getDmoz(),
- 'logo' => \Piwik\Plugins\Referrers\getSearchEngineLogoFromUrl('http://dmoz.org'),
- 'id' => 'dmoz',
- );
+ foreach ($metrics as $metric) {
+ if (!$metric instanceof Metric) {
+ continue;
+ }
- if ($dmozRank['rank'] > 0) {
- $data['SEO_Dmoz'] = $dmozRank;
+ $label = Piwik::translate($metric->getName());
+ $translated[$label] = array(
+ 'id' => $metric->getId(),
+ 'rank' => $metric->getValue(),
+ 'logo' => $metric->getLogo(),
+ 'logo_link' => $metric->getLogoLink(),
+ 'logo_tooltip' => Piwik::translate($metric->getLogoTooltip()),
+ 'rank_suffix' => Piwik::translate($metric->getValueSuffix()),
+ );
}
- return $data;
+ return DataTable::makeFromIndexedArray($translated);
}
}
diff --git a/plugins/SEO/Metric/Aggregator.php b/plugins/SEO/Metric/Aggregator.php
new file mode 100644
index 0000000000..69f9139d1e
--- /dev/null
+++ b/plugins/SEO/Metric/Aggregator.php
@@ -0,0 +1,63 @@
+<?php
+/**
+ * Piwik - free/libre analytics platform
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+
+namespace Piwik\Plugins\SEO\Metric;
+
+use Piwik\Container\StaticContainer;
+use Piwik\Metrics\Formatter;
+use Piwik\Piwik;
+
+/**
+ * Aggregates metrics from several providers.
+ */
+class Aggregator implements MetricsProvider
+{
+ /**
+ * @var MetricsProvider[]
+ */
+ private $providers;
+
+ public function __construct()
+ {
+ $this->providers = $this->getProviders();
+ }
+
+ public function getMetrics($domain)
+ {
+ $metrics = array();
+
+ foreach ($this->providers as $provider) {
+ $metrics = array_merge($metrics, $provider->getMetrics($domain));
+ }
+
+ return $metrics;
+ }
+
+ private function getProviders()
+ {
+ $container = StaticContainer::getContainer();
+
+ $providers = array(
+ $container->get('Piwik\Plugins\SEO\Metric\Google'),
+ $container->get('Piwik\Plugins\SEO\Metric\Bing'),
+ $container->get('Piwik\Plugins\SEO\Metric\Alexa'),
+ $container->get('Piwik\Plugins\SEO\Metric\DomainAge'),
+ $container->get('Piwik\Plugins\SEO\Metric\Majestic'),
+ $container->get('Piwik\Plugins\SEO\Metric\Dmoz'),
+ );
+
+ /**
+ * Use this event to register new SEO metrics providers.
+ *
+ * @param array $providers Contains an array of Piwik\Plugins\SEO\Metric\MetricsProvider instances.
+ */
+ Piwik::postEvent('SEO.getMetricsProviders', array(&$providers));
+
+ return $providers;
+ }
+}
diff --git a/plugins/SEO/Metric/Alexa.php b/plugins/SEO/Metric/Alexa.php
new file mode 100644
index 0000000000..d9ec020954
--- /dev/null
+++ b/plugins/SEO/Metric/Alexa.php
@@ -0,0 +1,51 @@
+<?php
+/**
+ * Piwik - free/libre analytics platform
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+
+namespace Piwik\Plugins\SEO\Metric;
+
+use Piwik\Http;
+use Psr\Log\LoggerInterface;
+
+/**
+ * Retrieves the Alexa rank.
+ */
+class Alexa implements MetricsProvider
+{
+ const URL = 'http://data.alexa.com/data?cli=10&url=';
+ const LINK = 'http://www.alexa.com/siteinfo/';
+
+ /**
+ * @var LoggerInterface
+ */
+ private $logger;
+
+ public function __construct(LoggerInterface $logger)
+ {
+ $this->logger = $logger;
+ }
+
+ public function getMetrics($domain)
+ {
+ try {
+ $response = Http::sendHttpRequest(self::URL . urlencode($domain), $timeout = 10, @$_SERVER['HTTP_USER_AGENT']);
+
+ $xml = @simplexml_load_string($response);
+ $value = $xml ? (string)$xml->SD->POPULARITY['TEXT'] : null;
+ } catch (\Exception $e) {
+ $this->logger->warning('Error while getting Alexa SEO stats: {message}', array('message' => $e->getMessage()));
+ $value = null;
+ }
+
+ $logo = \Piwik\Plugins\Referrers\getSearchEngineLogoFromUrl('http://alexa.com');
+ $link = self::LINK . urlencode($domain);
+
+ return array(
+ new Metric('alexa', 'SEO_AlexaRank', $value, $logo, $link)
+ );
+ }
+}
diff --git a/plugins/SEO/Metric/Bing.php b/plugins/SEO/Metric/Bing.php
new file mode 100644
index 0000000000..c01b23d576
--- /dev/null
+++ b/plugins/SEO/Metric/Bing.php
@@ -0,0 +1,54 @@
+<?php
+/**
+ * Piwik - free/libre analytics platform
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+
+namespace Piwik\Plugins\SEO\Metric;
+
+use Piwik\Http;
+use Psr\Log\LoggerInterface;
+
+/**
+ * Fetches the number of pages indexed in Bing.
+ */
+class Bing implements MetricsProvider
+{
+ const URL = 'http://www.bing.com/search?mkt=en-US&q=site%3A';
+
+ /**
+ * @var LoggerInterface
+ */
+ private $logger;
+
+ public function __construct(LoggerInterface $logger)
+ {
+ $this->logger = $logger;
+ }
+
+ public function getMetrics($domain)
+ {
+ $url = self::URL . urlencode($domain);
+
+ try {
+ $response = str_replace('&nbsp;', ' ', Http::sendHttpRequest($url, $timeout = 10, @$_SERVER['HTTP_USER_AGENT']));
+
+ if (preg_match('#([0-9\,]+) results#i', $response, $p)) {
+ $pageCount = (int)str_replace(',', '', $p[1]);
+ } else {
+ $pageCount = 0;
+ }
+ } catch (\Exception $e) {
+ $this->logger->warning('Error while getting Bing SEO stats: {message}', array('message' => $e->getMessage()));
+ $pageCount = null;
+ }
+
+ $logo = \Piwik\Plugins\Referrers\getSearchEngineLogoFromUrl('http://bing.com');
+
+ return array(
+ new Metric('bing-index', 'SEO_Bing_IndexedPages', $pageCount, $logo, null, null, 'General_Pages')
+ );
+ }
+}
diff --git a/plugins/SEO/Metric/Dmoz.php b/plugins/SEO/Metric/Dmoz.php
new file mode 100644
index 0000000000..82ffd248ba
--- /dev/null
+++ b/plugins/SEO/Metric/Dmoz.php
@@ -0,0 +1,61 @@
+<?php
+/**
+ * Piwik - free/libre analytics platform
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+
+namespace Piwik\Plugins\SEO\Metric;
+
+use Piwik\Http;
+use Psr\Log\LoggerInterface;
+
+/**
+ * Retrieves the number of Dmoz.org entries.
+ */
+class Dmoz implements MetricsProvider
+{
+ const URL = 'http://www.dmoz.org/search?q=';
+
+ /**
+ * @var LoggerInterface
+ */
+ private $logger;
+
+ /**
+ * @param LoggerInterface $logger
+ */
+ public function __construct(LoggerInterface $logger)
+ {
+ $this->logger = $logger;
+ }
+
+ public function getMetrics($domain)
+ {
+ try {
+ $response = Http::sendHttpRequest(self::URL . urlencode($domain), $timeout = 10, @$_SERVER['HTTP_USER_AGENT']);
+
+ preg_match('#Open Directory Sites[^\(]+\([0-9]-[0-9]+ of ([0-9]+)\)#', $response, $p);
+ if (!empty($p[1])) {
+ $value = (int)$p[1];
+ } else {
+ $value = 0;
+ }
+
+ // Add DMOZ only if > 0 entries found
+ if ($value == 0) {
+ return array();
+ }
+ } catch (\Exception $e) {
+ $this->logger->warning('Error while getting Dmoz SEO stats: {message}', array('message' => $e->getMessage()));
+ $value = null;
+ }
+
+ $logo = \Piwik\Plugins\Referrers\getSearchEngineLogoFromUrl('http://dmoz.org');
+
+ return array(
+ new Metric('dmoz', 'SEO_Dmoz', $value, $logo)
+ );
+ }
+}
diff --git a/plugins/SEO/Metric/DomainAge.php b/plugins/SEO/Metric/DomainAge.php
new file mode 100644
index 0000000000..a059b49572
--- /dev/null
+++ b/plugins/SEO/Metric/DomainAge.php
@@ -0,0 +1,141 @@
+<?php
+/**
+ * Piwik - free/libre analytics platform
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+
+namespace Piwik\Plugins\SEO\Metric;
+
+use Piwik\Http;
+use Piwik\Metrics\Formatter;
+use Psr\Log\LoggerInterface;
+
+/**
+ * Fetches the domain age using archive.org, who.is and whois.com.
+ */
+class DomainAge implements MetricsProvider
+{
+ /**
+ * @var Formatter
+ */
+ private $formatter;
+
+ /**
+ * @var LoggerInterface
+ */
+ private $logger;
+
+ public function __construct(Formatter $formatter, LoggerInterface $logger)
+ {
+ $this->formatter = $formatter;
+ $this->logger = $logger;
+ }
+
+ public function getMetrics($domain)
+ {
+ $ages = array();
+
+ $age = $this->getAgeArchiveOrg($domain);
+ if ($age > 0) {
+ $ages[] = $age;
+ }
+
+ $age = $this->getAgeWhoIs($domain);
+ if ($age > 0) {
+ $ages[] = $age;
+ }
+
+ $age = $this->getAgeWhoisCom($domain);
+ if ($age > 0) {
+ $ages[] = $age;
+ }
+
+ if (count($ages) > 0) {
+ $value = min($ages);
+ $value = $this->formatter->getPrettyTimeFromSeconds(time() - $value, true);
+ } else {
+ $value = null;
+ }
+
+ return array(
+ new Metric('domain-age', 'SEO_DomainAge', $value, 'plugins/SEO/images/whois.png')
+ );
+ }
+
+ /**
+ * Returns the domain age archive.org lists for the current url
+ *
+ * @param string $domain
+ * @return int
+ */
+ private function getAgeArchiveOrg($domain)
+ {
+ $url = str_replace('www.', '', $domain);
+ $data = $this->getUrl('http://wayback.archive.org/web/*/' . urlencode($url));
+ preg_match('#<a href=\"([^>]*)' . preg_quote($url) . '/\">([^<]*)<\/a>#', $data, $p);
+ if (!empty($p[2])) {
+ $value = strtotime($p[2]);
+ if ($value === false) {
+ return 0;
+ }
+ return $value;
+ }
+ return 0;
+ }
+
+ /**
+ * Returns the domain age who.is lists for the current url
+ *
+ * @param string $domain
+ * @return int
+ */
+ private function getAgeWhoIs($domain)
+ {
+ $url = preg_replace('/^www\./', '', $domain);
+ $url = 'http://www.who.is/whois/' . urlencode($url);
+ $data = $this->getUrl($url);
+ preg_match('#(?:Creation Date|Created On|created|Registered on)\.*:\s*([ \ta-z0-9\/\-:\.]+)#si', $data, $p);
+ if (!empty($p[1])) {
+ $value = strtotime(trim($p[1]));
+ if ($value === false) {
+ return 0;
+ }
+ return $value;
+ }
+ return 0;
+ }
+
+ /**
+ * Returns the domain age whois.com lists for the current url
+ *
+ * @param string $domain
+ * @return int
+ */
+ private function getAgeWhoisCom($domain)
+ {
+ $url = preg_replace('/^www\./', '', $domain);
+ $url = 'http://www.whois.com/whois/' . urlencode($url);
+ $data = $this->getUrl($url);
+ preg_match('#(?:Creation Date|Created On|created):\s*([ \ta-z0-9\/\-:\.]+)#si', $data, $p);
+ if (!empty($p[1])) {
+ $value = strtotime(trim($p[1]));
+ if ($value === false) {
+ return 0;
+ }
+ return $value;
+ }
+ return 0;
+ }
+
+ private function getUrl($url)
+ {
+ try {
+ return str_replace('&nbsp;', ' ', Http::sendHttpRequest($url, $timeout = 10, @$_SERVER['HTTP_USER_AGENT']));
+ } catch (\Exception $e) {
+ $this->logger->warning('Error while getting SEO stats (domain age): {message}', array('message' => $e->getMessage()));
+ return '';
+ }
+ }
+}
diff --git a/plugins/SEO/Metric/Google.php b/plugins/SEO/Metric/Google.php
new file mode 100644
index 0000000000..84e021c2ea
--- /dev/null
+++ b/plugins/SEO/Metric/Google.php
@@ -0,0 +1,170 @@
+<?php
+/**
+ * Piwik - free/libre analytics platform
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+
+namespace Piwik\Plugins\SEO\Metric;
+
+use Piwik\Http;
+use Psr\Log\LoggerInterface;
+
+/**
+ * Retrieves Google PageRank.
+ */
+class Google implements MetricsProvider
+{
+ const SEARCH_URL = 'http://www.google.com/search?hl=en&q=site%3A';
+
+ /**
+ * @var LoggerInterface
+ */
+ private $logger;
+
+ /**
+ * @param LoggerInterface $logger
+ */
+ public function __construct(LoggerInterface $logger)
+ {
+ $this->logger = $logger;
+ }
+
+ public function getMetrics($domain)
+ {
+ $pageCount = $this->fetchIndexedPagesCount($domain);
+ $pageRank = $this->fetchPageRank($domain);
+
+ $logo = \Piwik\Plugins\Referrers\getSearchEngineLogoFromUrl('http://google.com');
+
+ return array(
+ new Metric('google-index', 'SEO_Google_IndexedPages', $pageCount, $logo, null, null, 'General_Pages'),
+ new Metric('pagerank', 'Google PageRank', $pageRank, $logo, null, null, '/10'),
+ );
+ }
+
+ public function fetchIndexedPagesCount($domain)
+ {
+ $url = self::SEARCH_URL . urlencode($domain);
+
+ try {
+ $response = str_replace('&nbsp;', ' ', Http::sendHttpRequest($url, $timeout = 10, @$_SERVER['HTTP_USER_AGENT']));
+
+ if (preg_match('#([0-9\,]+) results#i', $response, $p)) {
+ return (int)str_replace(',', '', $p[1]);
+ } else {
+ return 0;
+ }
+ } catch (\Exception $e) {
+ $this->logger->warning('Error while getting Google search SEO stats: {message}', array('message' => $e->getMessage()));
+ return null;
+ }
+ }
+
+ public function fetchPageRank($domain)
+ {
+ $chwrite = $this->checkHash($this->hashURL($domain));
+
+ $url = "http://toolbarqueries.google.com/tbr?client=navclient-auto&ch=" . $chwrite . "&features=Rank&q=info:" . $domain . "&num=100&filter=0";
+
+ try {
+ $response = Http::sendHttpRequest($url, $timeout = 10, @$_SERVER['HTTP_USER_AGENT']);
+
+ preg_match('#Rank_[0-9]:[0-9]:([0-9]+){1,}#si', $response, $p);
+
+ return isset($p[1]) ? $p[1] : null;
+ } catch (\Exception $e) {
+ $this->logger->warning('Error while getting Google PageRank for SEO stats: {message}', array('message' => $e->getMessage()));
+ return null;
+ }
+ }
+
+ /**
+ * Generate a hash for a url
+ *
+ * @param string $string
+ * @return int
+ */
+ private function hashURL($string)
+ {
+ $Check1 = $this->strToNum($string, 0x1505, 0x21);
+ $Check2 = $this->strToNum($string, 0, 0x1003F);
+
+ $Check1 >>= 2;
+ $Check1 = (($Check1 >> 4) & 0x3FFFFC0) | ($Check1 & 0x3F);
+ $Check1 = (($Check1 >> 4) & 0x3FFC00) | ($Check1 & 0x3FF);
+ $Check1 = (($Check1 >> 4) & 0x3C000) | ($Check1 & 0x3FFF);
+
+ $T1 = (((($Check1 & 0x3C0) << 4) | ($Check1 & 0x3C)) << 2) | ($Check2 & 0xF0F);
+ $T2 = (((($Check1 & 0xFFFFC000) << 4) | ($Check1 & 0x3C00)) << 0xA) | ($Check2 & 0xF0F0000);
+
+ return ($T1 | $T2);
+ }
+
+ /**
+ * Generate a checksum for the hash string
+ *
+ * @param int $hashnum
+ * @return string
+ */
+ private function checkHash($hashnum)
+ {
+ $CheckByte = 0;
+ $Flag = 0;
+
+ $HashStr = sprintf('%u', $hashnum);
+ $length = strlen($HashStr);
+
+ for ($i = $length - 1; $i >= 0; $i--) {
+ $Re = $HashStr{$i};
+ if (1 === ($Flag % 2)) {
+ $Re += $Re;
+ $Re = (int)($Re / 10) + ($Re % 10);
+ }
+ $CheckByte += $Re;
+ $Flag++;
+ }
+
+ $CheckByte %= 10;
+ if (0 !== $CheckByte) {
+ $CheckByte = 10 - $CheckByte;
+ if (1 === ($Flag % 2)) {
+ if (1 === ($CheckByte % 2)) {
+ $CheckByte += 9;
+ }
+ $CheckByte >>= 1;
+ }
+ }
+
+ return '7' . $CheckByte . $HashStr;
+ }
+
+ /**
+ * Convert numeric string to int
+ *
+ * @param string $Str
+ * @param int $Check
+ * @param int $Magic
+ * @return int
+ */
+ private function strToNum($Str, $Check, $Magic)
+ {
+ $Int32Unit = 4294967296; // 2^32
+
+ $length = strlen($Str);
+ for ($i = 0; $i < $length; $i++) {
+ $Check *= $Magic;
+ // If the float is beyond the boundaries of integer (usually +/- 2.15e+9 = 2^31),
+ // the result of converting to integer is undefined
+ // refer to http://www.php.net/manual/en/language.types.integer.php
+ if ($Check >= $Int32Unit) {
+ $Check = ($Check - $Int32Unit * (int)($Check / $Int32Unit));
+ //if the check less than -2^31
+ $Check = ($Check < -2147483648) ? ($Check + $Int32Unit) : $Check;
+ }
+ $Check += ord($Str{$i});
+ }
+ return $Check;
+ }
+}
diff --git a/plugins/SEO/MajesticClient.php b/plugins/SEO/Metric/Majestic.php
index b7a7eeddc8..f276232945 100644
--- a/plugins/SEO/MajesticClient.php
+++ b/plugins/SEO/Metric/Majestic.php
@@ -4,22 +4,54 @@
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
- *
*/
-namespace Piwik\Plugins\SEO;
-use Piwik\Common;
+namespace Piwik\Plugins\SEO\Metric;
+
use Piwik\Http;
+use Psr\Log\LoggerInterface;
/**
* Client for Majestic SEO's HTTP API.
- *
- * Hides the HTTP request sending logic.
*/
-class MajesticClient
+class Majestic implements MetricsProvider
{
const API_BASE = 'http://simpleapi.majesticseo.com/sapi/';
const API_KEY = 'ETHPYY'; // please only use this key within Piwik
+ const LOGO = 'plugins/SEO/images/majesticseo.png';
+
+ /**
+ * @var LoggerInterface
+ */
+ private $logger;
+
+ /**
+ * @param LoggerInterface $logger
+ */
+ public function __construct(LoggerInterface $logger)
+ {
+ $this->logger = $logger;
+ }
+
+ public function getMetrics($domain)
+ {
+ try {
+ $stats = $this->getBacklinkStats($domain);
+ $backlinks = $stats['backlink_count'];
+ $referrers = $stats['referrer_domains_count'];
+ } catch (\Exception $e) {
+ $this->logger->warning('Error while getting Majestic SEO stats: {message}', array('message' => $e->getMessage()));
+ $backlinks = null;
+ $referrers = null;
+ }
+
+ $link = $this->getLinkForUrl($domain);
+
+ return array(
+ new Metric('external-backlinks', 'SEO_ExternalBacklinks', $backlinks, self::LOGO, $link, 'SEO_ViewBacklinksOnMajesticSEO'),
+ new Metric('referrer-domains', 'SEO_ReferrerDomains', $referrers, self::LOGO, $link, 'SEO_ViewBacklinksOnMajesticSEO'),
+ );
+ }
/**
* Returns a URL that can be used to view all SEO data for a particular website.
@@ -28,7 +60,7 @@ class MajesticClient
* accessible for.
* @return string
*/
- public static function getLinkForUrl($targetSiteUrl)
+ private function getLinkForUrl($targetSiteUrl)
{
$domain = @parse_url($targetSiteUrl, PHP_URL_HOST);
return "http://www.majesticseo.com/reports/site-explorer/summary/$domain?IndexDataSource=F";
@@ -52,7 +84,7 @@ class MajesticClient
* If either stat is false, either the API returned an
* error, or the IP was blocked for this request.
*/
- public function getBacklinkStats($siteDomain, $timeout = 300)
+ private function getBacklinkStats($siteDomain, $timeout = 300)
{
$apiUrl = $this->getApiUrl($method = 'GetBacklinkStats', $args = array(
'items' => '1',
diff --git a/plugins/SEO/Metric/Metric.php b/plugins/SEO/Metric/Metric.php
new file mode 100644
index 0000000000..ec28b55138
--- /dev/null
+++ b/plugins/SEO/Metric/Metric.php
@@ -0,0 +1,145 @@
+<?php
+/**
+ * Piwik - free/libre analytics platform
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+
+namespace Piwik\Plugins\SEO\Metric;
+
+/**
+ * Describes a SEO metric.
+ */
+class Metric
+{
+ /**
+ * @var string
+ */
+ private $id;
+
+ /**
+ * @var string
+ */
+ private $name;
+
+ /**
+ * @var string
+ */
+ private $value;
+
+ /**
+ * @var string
+ */
+ private $logo;
+
+ /**
+ * @var string|null
+ */
+ private $logoLink;
+
+ /**
+ * @var string|null
+ */
+ private $logoTooltip;
+
+ /**
+ * @var string|null
+ */
+ private $valueSuffix;
+
+ /**
+ * @param string $id
+ * @param string $name Can be a string or a translation ID.
+ * @param string $value Rank value.
+ * @param string $logo URL to a logo.
+ * @param string|null $logoLink
+ * @param string|null $logoTooltip
+ * @param string|null $valueSuffix
+ */
+ public function __construct($id, $name, $value, $logo, $logoLink = null, $logoTooltip = null, $valueSuffix = null)
+ {
+ $this->id = $id;
+ $this->name = $name;
+ $this->value = $value;
+ $this->logo = $logo;
+ $this->logoLink = $logoLink;
+ $this->logoTooltip = $logoTooltip;
+ $this->valueSuffix = $valueSuffix;
+ }
+
+ /**
+ * @return string
+ */
+ public function getId()
+ {
+ return $this->id;
+ }
+
+ /**
+ * @return string
+ */
+ public function getName()
+ {
+ return $this->name;
+ }
+
+ /**
+ * @return string
+ */
+ public function getValue()
+ {
+ return $this->value;
+ }
+
+ /**
+ * @return string
+ */
+ public function getLogo()
+ {
+ return $this->logo;
+ }
+
+ /**
+ * @return string|null
+ */
+ public function getLogoLink()
+ {
+ return $this->logoLink;
+ }
+
+ /**
+ * @return string|null
+ */
+ public function getLogoTooltip()
+ {
+ return $this->logoTooltip;
+ }
+
+ /**
+ * @return null|string
+ */
+ public function getValueSuffix()
+ {
+ return $this->valueSuffix;
+ }
+
+ /**
+ * Allows the class to be serialized with var_export (in the cache).
+ *
+ * @param array $array
+ * @return Metric
+ */
+ public static function __set_state($array)
+ {
+ return new self(
+ $array['id'],
+ $array['name'],
+ $array['value'],
+ $array['logo'],
+ $array['logoLink'],
+ $array['logoTooltip'],
+ $array['valueSuffix']
+ );
+ }
+}
diff --git a/plugins/SEO/Metric/MetricsProvider.php b/plugins/SEO/Metric/MetricsProvider.php
new file mode 100644
index 0000000000..c737efb928
--- /dev/null
+++ b/plugins/SEO/Metric/MetricsProvider.php
@@ -0,0 +1,21 @@
+<?php
+/**
+ * Piwik - free/libre analytics platform
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+
+namespace Piwik\Plugins\SEO\Metric;
+
+/**
+ * Provides SEO metrics for a domain.
+ */
+interface MetricsProvider
+{
+ /**
+ * @param string $domain
+ * @return Metric[]
+ */
+ public function getMetrics($domain);
+}
diff --git a/plugins/SEO/Metric/ProviderCache.php b/plugins/SEO/Metric/ProviderCache.php
new file mode 100644
index 0000000000..cb03063281
--- /dev/null
+++ b/plugins/SEO/Metric/ProviderCache.php
@@ -0,0 +1,48 @@
+<?php
+/**
+ * Piwik - free/libre analytics platform
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+
+namespace Piwik\Plugins\SEO\Metric;
+
+use Piwik\Cache;
+
+/**
+ * Caches another provider.
+ */
+class ProviderCache implements MetricsProvider
+{
+ /**
+ * @var MetricsProvider
+ */
+ private $provider;
+
+ /**
+ * @var Cache\Lazy
+ */
+ private $cache;
+
+ public function __construct(MetricsProvider $provider)
+ {
+ $this->provider = $provider;
+ $this->cache = Cache::getLazyCache();
+ }
+
+ public function getMetrics($domain)
+ {
+ $cacheId = 'SEO_getRank_' . md5($domain);
+
+ $metrics = $this->cache->fetch($cacheId);
+
+ if (! is_array($metrics)) {
+ $metrics = $this->provider->getMetrics($domain);
+
+ $this->cache->save($cacheId, $metrics, 60 * 60 * 6);
+ }
+
+ return $metrics;
+ }
+}
diff --git a/plugins/SEO/RankChecker.php b/plugins/SEO/RankChecker.php
deleted file mode 100644
index 655c434fc9..0000000000
--- a/plugins/SEO/RankChecker.php
+++ /dev/null
@@ -1,378 +0,0 @@
-<?php
-/**
- * Piwik - free/libre analytics platform
- *
- * @link http://piwik.org
- * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
- *
- */
-namespace Piwik\Plugins\SEO;
-
-use Exception;
-use Piwik\Http;
-use Piwik\Log;
-use Piwik\Metrics\Formatter;
-
-/**
- * The functions below are derived/adapted from GetRank.org's
- * Free PageRank Script v2.0, released under GPL.
- *
- * @copyright Copyright (C) 2007 - 2010 GetRank.Org All rights reserved.
- * @link http://www.getrank.org/free-pagerank-script/
- * @license GPL
- */
-class RankChecker
-{
- private $url;
- private $majesticInfo = null;
- private $formatter = null;
-
- public function __construct($url)
- {
- $this->url = self::extractDomainFromUrl($url);
- $this->formatter = new Formatter();
- }
-
- /**
- * Extract domain from URL as the web services generally
- * expect only a domain name (i.e., no protocol, port, path, query, etc).
- *
- * @param string $url
- * @return string
- */
- public static function extractDomainFromUrl($url)
- {
- return preg_replace(
- array(
- '~^https?\://~si', // strip protocol
- '~[/:#?;%&].*~', // strip port, path, query, anchor, etc
- '~\.$~', // trailing period
- ),
- '', $url);
- }
-
- /**
- * Web service proxy that retrieves the content at the specified URL
- *
- * @param string $url
- * @return string
- */
- private function getPage($url)
- {
- try {
- return str_replace('&nbsp;', ' ', Http::sendHttpRequest($url, $timeout = 10, @$_SERVER['HTTP_USER_AGENT']));
- } catch (Exception $e) {
- return '';
- }
- }
-
- /**
- * Returns the google page rank for the current url
- *
- * @return int
- */
- public function getPageRank()
- {
- $chwrite = $this->CheckHash($this->HashURL($this->url));
-
- $url = "http://toolbarqueries.google.com/tbr?client=navclient-auto&ch=" . $chwrite . "&features=Rank&q=info:" . $this->url . "&num=100&filter=0";
- $data = $this->getPage($url);
- preg_match('#Rank_[0-9]:[0-9]:([0-9]+){1,}#si', $data, $p);
- $value = isset($p[1]) ? $p[1] : 0;
-
- return $value;
- }
-
- /**
- * Returns the alexa traffic rank for the current url
- *
- * @return int
- */
- public function getAlexaRank()
- {
- $xml = @simplexml_load_string($this->getPage('http://data.alexa.com/data?cli=10&url=' . urlencode($this->url)));
- return $xml ? $xml->SD->POPULARITY['TEXT'] : '';
- }
-
- /**
- * Returns the number of Dmoz.org entries for the current url
- *
- * @return int
- */
- public function getDmoz()
- {
- $url = 'http://www.dmoz.org/search?q=' . urlencode($this->url);
- $data = $this->getPage($url);
- preg_match('#Open Directory Sites[^\(]+\([0-9]-[0-9]+ of ([0-9]+)\)#', $data, $p);
- if (!empty($p[1])) {
- return (int)$p[1];
- }
- return 0;
- }
-
- /**
- * Returns the number of pages google holds in it's index for the current url
- *
- * @return int
- */
- public function getIndexedPagesGoogle()
- {
- $url = 'http://www.google.com/search?hl=en&q=site%3A' . urlencode($this->url);
- $data = $this->getPage($url);
- if (preg_match('#([0-9\,]+) results#i', $data, $p)) {
- $indexedPages = (int)str_replace(',', '', $p[1]);
- return $indexedPages;
- }
- return 0;
- }
-
- /**
- * Returns the number of pages bing holds in it's index for the current url
- *
- * @return int
- */
- public function getIndexedPagesBing()
- {
- $url = 'http://www.bing.com/search?mkt=en-US&q=site%3A' . urlencode($this->url);
- $data = $this->getPage($url);
- if (preg_match('#([0-9\,]+) results#i', $data, $p)) {
- return (int)str_replace(',', '', $p[1]);
- }
- return 0;
- }
-
- /**
- * Returns the domain age for the current url
- *
- * @return int
- */
- public function getAge()
- {
- $ageArchiveOrg = $this->_getAgeArchiveOrg();
- $ageWhoIs = $this->_getAgeWhoIs();
- $ageWhoisCom = $this->_getAgeWhoisCom();
-
- $ages = array();
-
- if ($ageArchiveOrg > 0) {
- $ages[] = $ageArchiveOrg;
- }
-
- if ($ageWhoIs > 0) {
- $ages[] = $ageWhoIs;
- }
-
- if ($ageWhoisCom > 0) {
- $ages[] = $ageWhoisCom;
- }
-
- if (count($ages) > 1) {
- $maxAge = min($ages);
- } else {
- $maxAge = array_shift($ages);
- }
-
- if ($maxAge) {
- return $this->formatter->getPrettyTimeFromSeconds(time() - $maxAge, true);
- }
- return false;
- }
-
- /**
- * Returns the number backlinks that link to the current site.
- *
- * @return int
- */
- public function getExternalBacklinkCount()
- {
- try {
- $majesticInfo = $this->getMajesticInfo();
- return $majesticInfo['backlink_count'];
- } catch (Exception $e) {
- Log::info($e);
- return 0;
- }
- }
-
- /**
- * Returns the number of referrer domains that link to the current site.
- *
- * @return int
- */
- public function getReferrerDomainCount()
- {
- try {
- $majesticInfo = $this->getMajesticInfo();
- return $majesticInfo['referrer_domains_count'];
- } catch (Exception $e) {
- Log::info($e);
- return 0;
- }
- }
-
- /**
- * Returns the domain age archive.org lists for the current url
- *
- * @return int
- */
- protected function _getAgeArchiveOrg()
- {
- $url = str_replace('www.', '', $this->url);
- $data = @$this->getPage('http://wayback.archive.org/web/*/' . urlencode($url));
- preg_match('#<a href=\"([^>]*)' . preg_quote($url) . '/\">([^<]*)<\/a>#', $data, $p);
- if (!empty($p[2])) {
- $value = strtotime($p[2]);
- if ($value === false) {
- return 0;
- }
- return $value;
- }
- return 0;
- }
-
- /**
- * Returns the domain age who.is lists for the current url
- *
- * @return int
- */
- protected function _getAgeWhoIs()
- {
- $url = preg_replace('/^www\./', '', $this->url);
- $url = 'http://www.who.is/whois/' . urlencode($url);
- $data = $this->getPage($url);
- preg_match('#(?:Creation Date|Created On|created|Registered on)\.*:\s*([ \ta-z0-9\/\-:\.]+)#si', $data, $p);
- if (!empty($p[1])) {
- $value = strtotime(trim($p[1]));
- if ($value === false) {
- return 0;
- }
- return $value;
- }
- return 0;
- }
-
- /**
- * Returns the domain age whois.com lists for the current url
- *
- * @return int
- */
- protected function _getAgeWhoisCom()
- {
- $url = preg_replace('/^www\./', '', $this->url);
- $url = 'http://www.whois.com/whois/' . urlencode($url);
- $data = $this->getPage($url);
- preg_match('#(?:Creation Date|Created On|created):\s*([ \ta-z0-9\/\-:\.]+)#si', $data, $p);
- if (!empty($p[1])) {
- $value = strtotime(trim($p[1]));
- if ($value === false) {
- return 0;
- }
- return $value;
- }
- return 0;
- }
-
- /**
- * Convert numeric string to int
- *
- * @see getPageRank()
- *
- * @param string $Str
- * @param int $Check
- * @param int $Magic
- * @return int
- */
- private function StrToNum($Str, $Check, $Magic)
- {
- $Int32Unit = 4294967296; // 2^32
-
- $length = strlen($Str);
- for ($i = 0; $i < $length; $i++) {
- $Check *= $Magic;
- // If the float is beyond the boundaries of integer (usually +/- 2.15e+9 = 2^31),
- // the result of converting to integer is undefined
- // refer to http://www.php.net/manual/en/language.types.integer.php
- if ($Check >= $Int32Unit) {
- $Check = ($Check - $Int32Unit * (int)($Check / $Int32Unit));
- //if the check less than -2^31
- $Check = ($Check < -2147483648) ? ($Check + $Int32Unit) : $Check;
- }
- $Check += ord($Str{$i});
- }
- return $Check;
- }
-
- /**
- * Generate a hash for a url
- *
- * @see getPageRank()
- *
- * @param string $String
- * @return int
- */
- private function HashURL($String)
- {
- $Check1 = $this->StrToNum($String, 0x1505, 0x21);
- $Check2 = $this->StrToNum($String, 0, 0x1003F);
-
- $Check1 >>= 2;
- $Check1 = (($Check1 >> 4) & 0x3FFFFC0) | ($Check1 & 0x3F);
- $Check1 = (($Check1 >> 4) & 0x3FFC00) | ($Check1 & 0x3FF);
- $Check1 = (($Check1 >> 4) & 0x3C000) | ($Check1 & 0x3FFF);
-
- $T1 = (((($Check1 & 0x3C0) << 4) | ($Check1 & 0x3C)) << 2) | ($Check2 & 0xF0F);
- $T2 = (((($Check1 & 0xFFFFC000) << 4) | ($Check1 & 0x3C00)) << 0xA) | ($Check2 & 0xF0F0000);
-
- return ($T1 | $T2);
- }
-
- /**
- * Generate a checksum for the hash string
- *
- * @see getPageRank()
- *
- * @param int $Hashnum
- * @return string
- */
- private function CheckHash($Hashnum)
- {
- $CheckByte = 0;
- $Flag = 0;
-
- $HashStr = sprintf('%u', $Hashnum);
- $length = strlen($HashStr);
-
- for ($i = $length - 1; $i >= 0; $i--) {
- $Re = $HashStr{$i};
- if (1 === ($Flag % 2)) {
- $Re += $Re;
- $Re = (int)($Re / 10) + ($Re % 10);
- }
- $CheckByte += $Re;
- $Flag++;
- }
-
- $CheckByte %= 10;
- if (0 !== $CheckByte) {
- $CheckByte = 10 - $CheckByte;
- if (1 === ($Flag % 2)) {
- if (1 === ($CheckByte % 2)) {
- $CheckByte += 9;
- }
- $CheckByte >>= 1;
- }
- }
-
- return '7' . $CheckByte . $HashStr;
- }
-
- private function getMajesticInfo()
- {
- if ($this->majesticInfo === null) {
- $client = new MajesticClient();
- $this->majesticInfo = $client->getBacklinkStats($this->url);
- }
-
- return $this->majesticInfo;
- }
-}
diff --git a/plugins/SEO/Widgets.php b/plugins/SEO/Widgets.php
index bf2e95e0a7..1f06c2ec8b 100644
--- a/plugins/SEO/Widgets.php
+++ b/plugins/SEO/Widgets.php
@@ -11,6 +11,7 @@ namespace Piwik\Plugins\SEO;
use Piwik\Common;
use Piwik\DataTable\Renderer;
use Piwik\Site;
+use Piwik\Url;
use Piwik\UrlHelper;
use Piwik\View;
@@ -41,7 +42,7 @@ class Widgets extends \Piwik\Plugin\Widgets
$dataTable = API::getInstance()->getRank($url);
$view = new View('@SEO/getRank');
- $view->urlToRank = RankChecker::extractDomainFromUrl($url);
+ $view->urlToRank = Url::getHostFromUrl($url);
/** @var \Piwik\DataTable\Renderer\Php $renderer */
$renderer = Renderer::factory('php');
diff --git a/plugins/SEO/templates/getRank.twig b/plugins/SEO/templates/getRank.twig
index 5233fc82ea..80bf40ef7b 100644
--- a/plugins/SEO/templates/getRank.twig
+++ b/plugins/SEO/templates/getRank.twig
@@ -22,25 +22,24 @@
{% endset %}
{{ 'SEO_SEORankingsFor'|translate(cleanUrl)|raw }}
<table cellspacing="2" style="margin:auto;line-height:1.5em;padding-top:10px;">
+
{% for rank in ranks %}
<tr>
-{% set seoLink %}{% if rank.logo_link is defined %}<a class="linkContent" href="?module=Proxy&action=redirect&url={{ rank.logo_link|url_encode }}"
+{% set seoLink %}{% if rank.logo_link is not empty %}<a class="linkContent" href="?module=Proxy&action=redirect&url={{ rank.logo_link|url_encode }}"
target="_blank"
{% if rank.logo_tooltip is not empty %}title="{{ rank.logo_tooltip }}"{% endif %}>{% endif %}{% endset %}
{% set majesticLink %}{{ seoLink }}Majestic</a>{% endset %}
- <td>{% if rank.logo_link is defined %}{{ seoLink|raw }}{% endif %}<img
+ <td>{% if rank.logo_link is not empty %}{{ seoLink|raw }}{% endif %}<img
style='vertical-align:middle;margin-right:6px;' src='{{ rank.logo }}' border='0'
- alt="{{ rank.label }}">{% if rank.logo_link is defined %}</a>{% endif %} {{ rank.label|replace({"Majestic":
+ alt="{{ rank.label }}">{% if rank.logo_link is not empty %}</a>{% endif %} {{ rank.label|replace({"Majestic":
majesticLink})|raw }}
</td>
<td>
<div style="margin-left:15px;">
- {% if rank.logo_link is defined %}{{ seoLink|raw }}{% endif %}
+ {% if rank.logo_link is not empty %}{{ seoLink|raw }}{% endif %}
{% if rank.rank %}{{ rank.rank|raw }}{% else %}-{% endif %}
- {% if rank.id=='pagerank' %} /10
- {% elseif rank.id=='google-index' or rank.id=='bing-index' %} {{ 'General_Pages'|translate }}
- {% endif %}
- {% if rank.logo_link is defined %}</a>{% endif %}
+ {{ rank.rank_suffix }}
+ {% if rank.logo_link is not empty %}</a>{% endif %}
</div>
</td>
</tr>