/home/edulekha/studygroup.edulekha.com/ow_system_plugins/base/bol/seo_service.php
<?php
/**
* EXHIBIT A. Common Public Attribution License Version 1.0
* The contents of this file are subject to the Common Public Attribution License Version 1.0 (the “License”);
* you may not use this file except in compliance with the License. You may obtain a copy of the License at
* http://www.oxwall.org/license. The License is based on the Mozilla Public License Version 1.1
* but Sections 14 and 15 have been added to cover use of software over a computer network and provide for
* limited attribution for the Original Developer. In addition, Exhibit A has been modified to be consistent
* with Exhibit B. Software distributed under the License is distributed on an “AS IS” basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the specific language
* governing rights and limitations under the License. The Original Code is Oxwall software.
* The Initial Developer of the Original Code is Oxwall Foundation (http://www.oxwall.org/foundation).
* All portions of the code written by Oxwall Foundation are Copyright (c) 2011. All Rights Reserved.
* EXHIBIT B. Attribution Information
* Attribution Copyright Notice: Copyright 2011 Oxwall Foundation. All rights reserved.
* Attribution Phrase (not exceeding 10 words): Powered by Oxwall community software
* Attribution URL: http://www.oxwall.org/
* Graphic Image as provided in the Covered Code.
* Display of Attribution Information is required in Larger Works which are defined in the CPAL as a work
* which combines Covered Code or portions thereof with code not governed by the terms of the CPAL.
*/
/**
* Seo service.
*
* @author Alex Ermashev <alexermashev@gmail.com>
* @package ow_system_plugins.base.bol
* @method static BOL_SeoService getInstance()
* @since 1.8.4
*/
class BOL_SeoService
{
use OW_Singleton;
/**
* Sitemap item update weekly
*/
const SITEMAP_ITEM_UPDATE_WEEKLY = 'weekly';
/**
* Sitemap item update daily
*/
const SITEMAP_ITEM_UPDATE_DAILY = 'daily';
/**
* Sitemap file name
*/
const SITEMAP_FILE_NAME = 'sitemap%s.xml';
/**
* Sitemap dir name
*/
const SITEMAP_DIR_NAME = 'sitemap';
/**
* Sitemap update daily
*/
const SITEMAP_UPDATE_DAILY = 'daily';
/**
* Sitemap update weekly
*/
const SITEMAP_UPDATE_WEEKLY = 'weekly';
/**
* Sitemap update monthly
*/
const SITEMAP_UPDATE_MONTHLY = 'monthly';
/**
* Meta title max length
*/
const META_TITLE_MAX_LENGTH = 70;
/**
* Meta description max length
*/
const META_DESC_MAX_LENGTH = 150;
/**
* Sitemap
*
* @var BOL_SitemapDao
*/
protected $sitemapDao;
/**
* Constructor.
*/
private function __construct()
{
$this->sitemapDao = BOL_SitemapDao::getInstance();
}
/**
* Get sitemap url
*
* @param integer $part
* @return string
*/
public function getSitemapUrl($part = null)
{
$url = OW::getRouter()->urlForRoute('base.sitemap');
return $part
? $url . '?part=' . $part
: $url;
}
/**
* Get sitemap path
*
* @param integer $part
* @return string
*/
public function getSitemapPath($part = null)
{
$sitemapBuild = (int) OW::getConfig()->getValue('base', 'seo_sitemap_last_build');
$sitemapPath = $this->getBaseSitemapPath() . $sitemapBuild . '/';
return $sitemapPath . sprintf(self::SITEMAP_FILE_NAME, $part);
}
/**
* Get base sitemap path
*
* @return string
*/
protected function getBaseSitemapPath()
{
$path = OW::getPluginManager()->getPlugin('base')->getUserFilesDir() . self::SITEMAP_DIR_NAME . '/';
if ( !file_exists($path) )
{
mkdir($path);
@chmod($path, 0777);
}
return $path;
}
/**
* Escape url
*
* @param string $url
* @return string
*/
protected function escapeSitemapUrl($url)
{
return htmlspecialchars($url, ENT_QUOTES | ENT_XML1);
}
/**
* Generate sitemap
*
* @return void
*/
public function generateSitemap()
{
$isAllEntitiesFetched = true;
// don't collect urls while sitemap is building
if ( !(int) OW::getConfig()->getValue('base', 'seo_sitemap_build_in_progress') )
{
OW::getConfig()->saveConfig('base', 'seo_sitemap_build_finished', 0);
// get sitemap entities
$entities = $this->getSitemapEntities();
$maxCount = (int) OW::getConfig()->getValue('base', 'seo_sitemap_entitites_max_count');
$limit = (int) OW::getConfig()->getValue('base', 'seo_sitemap_entitites_limit');
if ( $entities )
{
// fetch urls
foreach ( $entities as $entityType => $entityData )
{
// skip all disabled entities
if ( !$entityData['enabled'] )
{
continue;
}
// get sitemap items
foreach ( $entityData['items'] as $item )
{
// skip already fetched items
if ( $item['data_fetched'] )
{
continue;
}
// correct the limit value
if ( $item['urls_count'] + $limit > $maxCount )
{
$limit = $maxCount - $item['urls_count'];
}
// get urls
$event = new OW_Event('base.sitemap.get_urls', array(
'entity' => $item['name'],
'limit' => $limit,
'offset' => $item['urls_count']
));
OW::getEventManager()->trigger($event);
$newUrlsCount = count($event->getData());
$totalUrlsCount = (int) $item['urls_count'] + $newUrlsCount;
$isAllEntitiesFetched = false;
!$newUrlsCount || $newUrlsCount != $limit || $totalUrlsCount >= $maxCount
? $this->updateSitemapEntityItem($entityType, $item['name'], true, $totalUrlsCount)
: $this->updateSitemapEntityItem($entityType, $item['name'], false, $totalUrlsCount);
// add new urls
if ( $newUrlsCount )
{
// process received urls
foreach ( $event->getData() as $url )
{
if ( $this->isSitemapUrlUnique($url) )
{
$this->addSiteMapUrl($url, $entityType);
}
}
}
// we process at a time only one entity item
break 2;
}
}
}
}
// build sitemap
if ( $isAllEntitiesFetched )
{
$this->buildSitemap();
}
}
/**
* Build sitemap
*
* @return void
*/
protected function buildSitemap()
{
OW::getConfig()->saveConfig('base', 'seo_sitemap_build_in_progress', 1);
$urls = $this->sitemapDao->findUrlList( (int) OW::getConfig()->getValue('base', 'seo_sitemap_max_urls_in_file') );
$newSitemapBuild = (int) OW::getConfig()->getValue('base', 'seo_sitemap_last_build') + 1;
$entities = $this->getSitemapEntities();
$sitemapIndex = (int) OW::getConfig()->getValue('base', 'seo_sitemap_index');
$newSitemapPath = $this->getBaseSitemapPath() . $newSitemapBuild . '/';
if ( !file_exists($newSitemapPath) )
{
mkdir($newSitemapPath);
@chmod($newSitemapPath, 0777);
}
// generate list of sitemaps
if ( $urls )
{
$urlsIds = array();
// generate parts of sitemap
$processedUrls = [];
$defaultLanguage = BOL_LanguageService::getInstance()->findDefault();
$activeLanguages = BOL_LanguageService::getInstance()->findActiveList();
$activeLanguagesCount = count($activeLanguages);
// process urls
foreach( $urls as $urlData )
{
$urlsIds[] = $urlData['id'];
if ( $activeLanguagesCount > 1 )
{
// process active languages
foreach( $activeLanguages as $language )
{
$mainUrl = null;
// get main url
if ( $language->id == $defaultLanguage->id )
{
$mainUrl = $urlData['url']; // don't include a lang param for default language
}
else {
$mainUrl = strstr($urlData['url'], '?')
? $urlData['url'] . '&language_id=' . $language->id
: $urlData['url'] . '?language_id=' . $language->id;
}
// process alternate languages
$alternateLanguages = array();
foreach( $activeLanguages as $altLanguage )
{
if ( $altLanguage->id == $defaultLanguage->id )
{
$alternateLanguages[] = array(
'url' => $this->escapeSitemapUrl($urlData['url']),
'code' => $altLanguage->tag
);
}
else
{
$alternateLanguages[] = array(
'url' => strstr($urlData['url'], '?')
? $this->escapeSitemapUrl($urlData['url'] . '&language_id=' . $altLanguage->id)
: $this->escapeSitemapUrl($urlData['url'] . '?language_id=' . $altLanguage->id),
'code' => $altLanguage->tag
);
}
}
$processedUrls[] = array(
'url' => $this->escapeSitemapUrl($mainUrl),
'changefreq' => $entities[$urlData['entityType']]['changefreq'],
'priority' => $entities[$urlData['entityType']]['priority'],
'alternateLanguages' => $alternateLanguages
);
}
}
else
{
$processedUrls[] = array(
'url' => $this->escapeSitemapUrl($urlData['url']),
'changefreq' => $entities[$urlData['entityType']]['changefreq'],
'priority' => $entities[$urlData['entityType']]['priority'],
'alternateLanguages' => array()
);
}
}
// delete processed urls
$urlsIds = array_chunk($urlsIds, 500);
foreach( $urlsIds as $urlList )
{
$this->sitemapDao->deleteByIdList($urlList);
}
// render data
$view = new OW_View();
$view->setTemplate(OW::getPluginManager()->getPlugin('base')->getViewDir() . 'sitemap_part.xml');
$view->assign('urls', $processedUrls);
// save data in a file
file_put_contents($newSitemapPath . sprintf(self::SITEMAP_FILE_NAME, $sitemapIndex + 1), $view->render());
OW::getConfig()->saveConfig('base', 'seo_sitemap_index', $sitemapIndex + 1);
return;
}
// generate a final sitemap index file
if ( $sitemapIndex )
{
$sitemapParts = array();
$lastModDate = date('c', time());
for ( $i = 1; $i <= $sitemapIndex; $i++ )
{
$sitemapParts[] = array(
'url' => $this->escapeSitemapUrl($this->getSitemapUrl($i)),
'lastmod' => $lastModDate
);
}
// render data
$view = new OW_View();
$view->setTemplate(OW::getPluginManager()->getPlugin('base')->getViewDir() . 'sitemap.xml');
$view->assign('urls', $sitemapParts);
// save data in a file
file_put_contents($newSitemapPath . sprintf(self::SITEMAP_FILE_NAME, ''), $view->render());
// update configs
OW::getConfig()->saveConfig('base', 'seo_sitemap_index', 0);
OW::getConfig()->saveConfig('base', 'seo_sitemap_last_start', time());
OW::getConfig()->saveConfig('base', 'seo_sitemap_last_build', $newSitemapBuild);
// truncate table
$this->sitemapDao->truncate();
}
// clear all entities
foreach ( $entities as $entityType => $entityData )
{
foreach ( $entityData['items'] as $item )
{
$this->updateSitemapEntityItem($entityType, $item['name'], false, 0);
}
}
// remove a previous build
$previousBuildPath = $this->getBaseSitemapPath() . ($newSitemapBuild - 1) . '/';
if ( file_exists($previousBuildPath) )
{
UTIL_File::removeDir($previousBuildPath);
}
OW::getConfig()->saveConfig('base', 'seo_sitemap_build_in_progress', 0);
OW::getConfig()->saveConfig('base', 'seo_sitemap_build_finished', 1);
}
/**
* Is sitemap ready for the next build
*
* @return boolean
*/
public function isSitemapReadyForNextBuild()
{
$lastStart = (int) OW::getConfig()->getValue('base', 'seo_sitemap_last_start');
$scheduleUpdate = OW::getConfig()->getValue('base', 'seo_sitemap_schedule_update');
if ( !$lastStart )
{
return true;
}
$secondsInDay = 86400;
switch($scheduleUpdate)
{
case self::SITEMAP_UPDATE_MONTHLY :
$delaySeconds = $secondsInDay * 30;
break;
case self::SITEMAP_UPDATE_WEEKLY :
$delaySeconds = $secondsInDay * 6;
break;
case self::SITEMAP_UPDATE_DAILY:
default:
$delaySeconds = $secondsInDay;
}
return $lastStart - time() >= $delaySeconds;
}
/**
* Get sitemap entities
*
* @return array
*/
public function getSitemapEntities()
{
return json_decode(OW::getConfig()->getValue('base', 'seo_sitemap_entities'), true);
}
/**
* Add sitemap entity
*
* @param string $langPrefix
* @param string $label
* @param string $entityType
* @param string $description
* @param array $items
* @param float $priority
* @param string $changeFreq
* @return void
*/
public function addSitemapEntity($langPrefix, $label, $entityType, array $items, $description = null, $priority = 0.5, $changeFreq = self::SITEMAP_ITEM_UPDATE_WEEKLY)
{
$entities = $this->getSitemapEntities();
if ( !array_key_exists($entityType, $entities) )
{
// process items
$processedItems = array();
foreach ($items as $item) {
$processedItems[] = array(
'name' => $item,
'data_fetched' => false,
'urls_count' => 0,
);
}
$entities[$entityType] = array(
'lang_prefix' => $langPrefix,
'label' => $label,
'description' => $description,
'items' => $processedItems,
'enabled' => true,
'priority' => $priority,
'changefreq' => $changeFreq
);
OW::getConfig()->saveConfig('base', 'seo_sitemap_entities', json_encode($entities));
}
}
/**
* Enable sitemap entity
*
* @param string $entityType
* @return void
*/
public function enableSitemapEntity($entityType)
{
$this->setSitemapEntityStatus($entityType);
}
/**
* Disable sitemap entity
*
* @param string $entityType
* @return void
*/
public function disableSitemapEntity($entityType)
{
$this->setSitemapEntityStatus($entityType, false);
}
/**
* Remove sitemap entity
*
* @param string $entityType
* @return void
*/
public function removeSitemapEntity($entityType)
{
$entities = $this->getSitemapEntities();
if ( array_key_exists($entityType, $entities) )
{
unset($entities[$entityType]);
OW::getConfig()->saveConfig('base', 'seo_sitemap_entities', json_encode($entities));
// delete already collected data
$this->deleteSitemapUrls($entityType);
}
}
protected $metaData;
/**
* @return array
*/
public function getMetaData()
{
if( $this->metaData === null )
{
$this->metaData = json_decode(OW::getConfig()->getValue("base", "seo_meta_info"), true);
}
return $this->metaData;
}
/**
* @param array $data
*/
public function setMetaData( array $data )
{
$this->metaData = $data;
OW::getConfig()->saveConfig("base", "seo_meta_info", json_encode($data));
}
/**
* @param $sectionKey
* @param string $entityKey
* @return bool
*/
public function isMetaDisabledForEntity( $sectionKey, $entityKey )
{
return isset($this->getMetaData()["disabledEntities"][$sectionKey]) && in_array($entityKey, $this->getMetaData()["disabledEntities"][$sectionKey]);
}
/**
* @param BOL_User $userDto
* @return array
*/
public function getUserMetaInfo( BOL_User $userDto )
{
$result = array("user_name" => $userDto->getUsername());
$data = BOL_QuestionService::getInstance()->getQuestionData(array($userDto->getId()), array("sex", "birthdate", "googlemap_location"))[$userDto->getId()];
if( !empty($data["sex"]) )
{
$result["user_gender"] = BOL_QuestionService::getInstance()->getQuestionValueLang("sex", $data["sex"]);
}
if( !empty($data["birthdate"]) )
{
$date = UTIL_DateTime::parseDate($data["birthdate"], UTIL_DateTime::MYSQL_DATETIME_DATE_FORMAT);
$result["user_age"] = UTIL_DateTime::getAge($date['year'], $date['month'], $date['day']);
}
if( !empty($data["googlemap_location"]["address"]) )
{
$result["user_location"] = trim($data["googlemap_location"]["address"]);
}
return $result;
}
/**
* @param $path
* @param $name
*/
public function saveSocialLogo( $path, $name )
{
OW::getStorage()->copyFile($path, OW::getPluginManager()->getPlugin("base")->getUserFilesDir().$name);
OW::getConfig()->saveConfig("base", "seo_social_meta_logo_name", $name);
}
/**
* @return string
*/
public function getSocialLogoUrl()
{
$fileName = OW::getConfig()->getValue("base", "seo_social_meta_logo_name");
if( !$fileName )
{
return null;
}
return OW::getStorage()->getFileUrl(OW::getPluginManager()->getPlugin("base")->getUserFilesDir().$fileName);
}
/**
* Delete sitemap urls
*
* @param string $entityType
* @return void
*/
protected function deleteSitemapUrls($entityType)
{
$example = new OW_Example();
$example->andFieldEqual('entityType', $entityType);
$this->sitemapDao->deleteByExample($example);
}
/**
* Set sitemap entity status
*
* @param string $entityType
* @param boolean $enabled
* @return void
*/
protected function setSitemapEntityStatus($entityType, $enabled = true)
{
$entities = $this->getSitemapEntities();
if ( array_key_exists($entityType, $entities) )
{
$entities[$entityType]['enabled'] = $enabled;
OW::getConfig()->saveConfig('base', 'seo_sitemap_entities', json_encode($entities));
if ( !$enabled )
{
// clear entity items
foreach ( $entities[$entityType]['items'] as $item )
{
$this->updateSitemapEntityItem($entityType, $item['name'], false, 0);
}
// delete already collected urls
$this->deleteSitemapUrls($entityType);
}
}
}
/**
* Is sitemap url unique
*
* @param $url
* @return bool
*/
protected function isSitemapUrlUnique($url)
{
$example = new OW_Example();
$example->andFieldEqual('url', $url);
return !$this->sitemapDao->countByExample($example);
}
/**
* Add sitemap url
*
* @param string $url
* @param string $entityType
* @return void
*/
protected function addSiteMapUrl($url, $entityType)
{
$sitemapDto = new BOL_Sitemap();
$sitemapDto->url = $url;
$sitemapDto->entityType = $entityType;
$this->sitemapDao->save($sitemapDto);
}
/**
* Update sitemap entity item
*
* @param string $entityType
* @param string $itemName
* @param boolean $isDataFetched
* @param integer $urlsCount
* @return void
*/
protected function updateSitemapEntityItem($entityType, $itemName, $isDataFetched, $urlsCount = 0)
{
$entities = $this->getSitemapEntities();
if ( array_key_exists($entityType, $entities) )
{
foreach ( $entities[$entityType]['items'] as &$item )
{
if ( $itemName == $item['name'] )
{
$item['data_fetched'] = $isDataFetched;
$item['urls_count'] = $urlsCount;
break;
}
}
OW::getConfig()->saveConfig('base', 'seo_sitemap_entities', json_encode($entities));
}
}
}