PageRenderTime 68ms CodeModel.GetById 26ms RepoModel.GetById 0ms app.codeStats 0ms

/blog/wp-content/plugins/google-sitemap-generator/sitemap-core.php

https://bitbucket.org/chileagil/website
PHP | 2606 lines | 1599 code | 365 blank | 642 comment | 325 complexity | 1a4dd73798991d6a04e92841f58f1a8c MD5 | raw file
Possible License(s): AGPL-1.0, GPL-2.0, LGPL-2.1
  1. <?php
  2. /*
  3. $Id: sitemap-core.php 224413 2010-04-02 15:54:52Z arnee $
  4. */
  5. //Enable for dev! Good code doesn't generate any notices...
  6. //error_reporting(E_ALL);
  7. //ini_set("display_errors",1);
  8. /**
  9. * Represents the status (success and failures) of a building process
  10. * @author Arne Brachhold
  11. * @package sitemap
  12. * @since 3.0b5
  13. */
  14. class GoogleSitemapGeneratorStatus {
  15. function GoogleSitemapGeneratorStatus() {
  16. $this->_startTime = $this->GetMicrotimeFloat();
  17. $exists = get_option("sm_status");
  18. if($exists === false) add_option("sm_status","","Status","no");
  19. $this->Save();
  20. }
  21. function Save() {
  22. update_option("sm_status",$this);
  23. }
  24. /**
  25. * Returns the last saved status object or null
  26. *
  27. * @return GoogleSitemapGeneratorStatus
  28. */
  29. function &Load() {
  30. $status = @get_option("sm_status");
  31. if(is_a($status,"GoogleSitemapGeneratorStatus")) return $status;
  32. else return null;
  33. }
  34. /**
  35. * @var float $_startTime The start time of the building process
  36. * @access private
  37. */
  38. var $_startTime = 0;
  39. /**
  40. * @var float $_endTime The end time of the building process
  41. * @access private
  42. */
  43. var $_endTime = 0;
  44. /**
  45. * @var bool $$_hasChanged Indicates if the sitemap content has changed
  46. * @access private
  47. */
  48. var $_hasChanged = true;
  49. /**
  50. * @var int $_memoryUsage The amount of memory used in bytes
  51. * @access private
  52. */
  53. var $_memoryUsage = 0;
  54. /**
  55. * @var int $_lastPost The number of posts processed. This value is updated every 50 posts.
  56. * @access private
  57. */
  58. var $_lastPost = 0;
  59. /**
  60. * @var int $_lastTime The time when the last step-update occured. This value is updated every 50 posts.
  61. * @access private
  62. */
  63. var $_lastTime = 0;
  64. function End($hasChanged = true) {
  65. $this->_endTime = $this->GetMicrotimeFloat();
  66. $this->SetMemoryUsage();
  67. $this->_hasChanged = $hasChanged;
  68. $this->Save();
  69. }
  70. function SetMemoryUsage() {
  71. if(function_exists("memory_get_peak_usage")) {
  72. $this->_memoryUsage = memory_get_peak_usage(true);
  73. } else if(function_exists("memory_get_usage")) {
  74. $this->_memoryUsage = memory_get_usage(true);
  75. }
  76. }
  77. function GetMemoryUsage() {
  78. return round($this->_memoryUsage / 1024 / 1024,2);
  79. }
  80. function SaveStep($postCount) {
  81. $this->SetMemoryUsage();
  82. $this->_lastPost = $postCount;
  83. $this->_lastTime = $this->GetMicrotimeFloat();
  84. $this->Save();
  85. }
  86. function GetTime() {
  87. return round($this->_endTime - $this->_startTime,2);
  88. }
  89. function GetStartTime() {
  90. return round($this->_startTime, 2);
  91. }
  92. function GetLastTime() {
  93. return round($this->_lastTime - $this->_startTime,2);
  94. }
  95. function GetLastPost() {
  96. return $this->_lastPost;
  97. }
  98. var $_usedXml = false;
  99. var $_xmlSuccess = false;
  100. var $_xmlPath = '';
  101. var $_xmlUrl = '';
  102. function StartXml($path,$url) {
  103. $this->_usedXml = true;
  104. $this->_xmlPath = $path;
  105. $this->_xmlUrl = $url;
  106. $this->Save();
  107. }
  108. function EndXml($success) {
  109. $this->_xmlSuccess = $success;
  110. $this->Save();
  111. }
  112. var $_usedZip = false;
  113. var $_zipSuccess = false;
  114. var $_zipPath = '';
  115. var $_zipUrl = '';
  116. function StartZip($path,$url) {
  117. $this->_usedZip = true;
  118. $this->_zipPath = $path;
  119. $this->_zipUrl = $url;
  120. $this->Save();
  121. }
  122. function EndZip($success) {
  123. $this->_zipSuccess = $success;
  124. $this->Save();
  125. }
  126. var $_usedGoogle = false;
  127. var $_googleUrl = '';
  128. var $_gooogleSuccess = false;
  129. var $_googleStartTime = 0;
  130. var $_googleEndTime = 0;
  131. function StartGooglePing($url) {
  132. $this->_googleUrl = $url;
  133. $this->_usedGoogle = true;
  134. $this->_googleStartTime = $this->GetMicrotimeFloat();
  135. $this->Save();
  136. }
  137. function EndGooglePing($success) {
  138. $this->_googleEndTime = $this->GetMicrotimeFloat();
  139. $this->_gooogleSuccess = $success;
  140. $this->Save();
  141. }
  142. function GetGoogleTime() {
  143. return round($this->_googleEndTime - $this->_googleStartTime,2);
  144. }
  145. var $_usedYahoo = false;
  146. var $_yahooUrl = '';
  147. var $_yahooSuccess = false;
  148. var $_yahooStartTime = 0;
  149. var $_yahooEndTime = 0;
  150. function StartYahooPing($url) {
  151. $this->_yahooUrl = $url;
  152. $this->_usedYahoo = true;
  153. $this->_yahooStartTime = $this->GetMicrotimeFloat();
  154. $this->Save();
  155. }
  156. function EndYahooPing($success) {
  157. $this->_yahooEndTime = $this->GetMicrotimeFloat();
  158. $this->_yahooSuccess = $success;
  159. $this->Save();
  160. }
  161. function GetYahooTime() {
  162. return round($this->_yahooEndTime - $this->_yahooStartTime,2);
  163. }
  164. var $_usedAsk = false;
  165. var $_askUrl = '';
  166. var $_askSuccess = false;
  167. var $_askStartTime = 0;
  168. var $_askEndTime = 0;
  169. function StartAskPing($url) {
  170. $this->_usedAsk = true;
  171. $this->_askUrl = $url;
  172. $this->_askStartTime = $this->GetMicrotimeFloat();
  173. $this->Save();
  174. }
  175. function EndAskPing($success) {
  176. $this->_askEndTime = $this->GetMicrotimeFloat();
  177. $this->_askSuccess = $success;
  178. $this->Save();
  179. }
  180. function GetAskTime() {
  181. return round($this->_askEndTime - $this->_askStartTime,2);
  182. }
  183. var $_usedMsn = false;
  184. var $_msnUrl = '';
  185. var $_msnSuccess = false;
  186. var $_msnStartTime = 0;
  187. var $_msnEndTime = 0;
  188. function StartMsnPing($url) {
  189. $this->_usedMsn = true;
  190. $this->_msnUrl = $url;
  191. $this->_msnStartTime = $this->GetMicrotimeFloat();
  192. $this->Save();
  193. }
  194. function EndMsnPing($success) {
  195. $this->_msnEndTime = $this->GetMicrotimeFloat();
  196. $this->_msnSuccess = $success;
  197. $this->Save();
  198. }
  199. function GetMsnTime() {
  200. return round($this->_msnEndTime - $this->_msnStartTime,2);
  201. }
  202. function GetMicrotimeFloat() {
  203. list($usec, $sec) = explode(" ", microtime());
  204. return ((float)$usec + (float)$sec);
  205. }
  206. }
  207. /**
  208. * Represents an item in the page list
  209. * @author Arne Brachhold
  210. * @package sitemap
  211. * @since 3.0
  212. */
  213. class GoogleSitemapGeneratorPage {
  214. /**
  215. * @var string $_url Sets the URL or the relative path to the blog dir of the page
  216. * @access private
  217. */
  218. var $_url;
  219. /**
  220. * @var float $_priority Sets the priority of this page
  221. * @access private
  222. */
  223. var $_priority;
  224. /**
  225. * @var string $_changeFreq Sets the chanfe frequency of the page. I want Enums!
  226. * @access private
  227. */
  228. var $_changeFreq;
  229. /**
  230. * @var int $_lastMod Sets the lastMod date as a UNIX timestamp.
  231. * @access private
  232. */
  233. var $_lastMod;
  234. /**
  235. * Initialize a new page object
  236. *
  237. * @since 3.0
  238. * @access public
  239. * @author Arne Brachhold
  240. * @param bool $enabled Should this page be included in thesitemap
  241. * @param string $url The URL or path of the file
  242. * @param float $priority The Priority of the page 0.0 to 1.0
  243. * @param string $changeFreq The change frequency like daily, hourly, weekly
  244. * @param int $lastMod The last mod date as a unix timestamp
  245. */
  246. function GoogleSitemapGeneratorPage($url="",$priority=0.0,$changeFreq="never",$lastMod=0) {
  247. $this->SetUrl($url);
  248. $this->SetProprity($priority);
  249. $this->SetChangeFreq($changeFreq);
  250. $this->SetLastMod($lastMod);
  251. }
  252. /**
  253. * Returns the URL of the page
  254. *
  255. * @return string The URL
  256. */
  257. function GetUrl() {
  258. return $this->_url;
  259. }
  260. /**
  261. * Sets the URL of the page
  262. *
  263. * @param string $url The new URL
  264. */
  265. function SetUrl($url) {
  266. $this->_url=(string) $url;
  267. }
  268. /**
  269. * Returns the priority of this page
  270. *
  271. * @return float the priority, from 0.0 to 1.0
  272. */
  273. function GetPriority() {
  274. return $this->_priority;
  275. }
  276. /**
  277. * Sets the priority of the page
  278. *
  279. * @param float $priority The new priority from 0.1 to 1.0
  280. */
  281. function SetProprity($priority) {
  282. $this->_priority=floatval($priority);
  283. }
  284. /**
  285. * Returns the change frequency of the page
  286. *
  287. * @return string The change frequncy like hourly, weekly, monthly etc.
  288. */
  289. function GetChangeFreq() {
  290. return $this->_changeFreq;
  291. }
  292. /**
  293. * Sets the change frequency of the page
  294. *
  295. * @param string $changeFreq The new change frequency
  296. */
  297. function SetChangeFreq($changeFreq) {
  298. $this->_changeFreq=(string) $changeFreq;
  299. }
  300. /**
  301. * Returns the last mod of the page
  302. *
  303. * @return int The lastmod value in seconds
  304. */
  305. function GetLastMod() {
  306. return $this->_lastMod;
  307. }
  308. /**
  309. * Sets the last mod of the page
  310. *
  311. * @param int $lastMod The lastmod of the page
  312. */
  313. function SetLastMod($lastMod) {
  314. $this->_lastMod=intval($lastMod);
  315. }
  316. function Render() {
  317. if($this->_url == "/" || empty($this->_url)) return '';
  318. $r="";
  319. $r.= "\t<url>\n";
  320. $r.= "\t\t<loc>" . $this->EscapeXML($this->_url) . "</loc>\n";
  321. if($this->_lastMod>0) $r.= "\t\t<lastmod>" . date('Y-m-d\TH:i:s+00:00',$this->_lastMod) . "</lastmod>\n";
  322. if(!empty($this->_changeFreq)) $r.= "\t\t<changefreq>" . $this->_changeFreq . "</changefreq>\n";
  323. if($this->_priority!==false && $this->_priority!=="") $r.= "\t\t<priority>" . number_format($this->_priority,1) . "</priority>\n";
  324. $r.= "\t</url>\n";
  325. return $r;
  326. }
  327. function EscapeXML($string) {
  328. return str_replace ( array ( '&', '"', "'", '<', '>'), array ( '&amp;' , '&quot;', '&apos;' , '&lt;' , '&gt;'), $string);
  329. }
  330. }
  331. class GoogleSitemapGeneratorXmlEntry {
  332. var $_xml;
  333. function GoogleSitemapGeneratorXmlEntry($xml) {
  334. $this->_xml = $xml;
  335. }
  336. function Render() {
  337. return $this->_xml;
  338. }
  339. }
  340. class GoogleSitemapGeneratorDebugEntry extends GoogleSitemapGeneratorXmlEntry {
  341. function Render() {
  342. return "<!-- " . $this->_xml . " -->\n";
  343. }
  344. }
  345. /**
  346. * Base class for all priority providers
  347. * @author Arne Brachhold
  348. * @package sitemap
  349. * @since 3.0
  350. */
  351. class GoogleSitemapGeneratorPrioProviderBase {
  352. /**
  353. * @var int $_totalComments The total number of comments of all posts
  354. * @access protected
  355. */
  356. var $_totalComments=0;
  357. /**
  358. * @var int $_totalComments The total number of posts
  359. * @access protected
  360. */
  361. var $_totalPosts=0;
  362. /**
  363. * Returns the (translated) name of this priority provider
  364. *
  365. * @since 3.0
  366. * @access public
  367. * @author Arne Brachhold
  368. * @return string The translated name
  369. */
  370. function GetName() {
  371. return "";
  372. }
  373. /**
  374. * Returns the (translated) description of this priority provider
  375. *
  376. * @since 3.0
  377. * @access public
  378. * @author Arne Brachhold
  379. * @return string The translated description
  380. */
  381. function GetDescription() {
  382. return "";
  383. }
  384. /**
  385. * Initializes a new priority provider
  386. *
  387. * @param $totalComments int The total number of comments of all posts
  388. * @param $totalPosts int The total number of posts
  389. * @since 3.0
  390. * @access public
  391. * @author Arne Brachhold
  392. */
  393. function GoogleSitemapGeneratorPrioProviderBase($totalComments,$totalPosts) {
  394. $this->_totalComments=$totalComments;
  395. $this->_totalPosts=$totalPosts;
  396. }
  397. /**
  398. * Returns the priority for a specified post
  399. *
  400. * @param $postID int The ID of the post
  401. * @param $commentCount int The number of comments for this post
  402. * @since 3.0
  403. * @access public
  404. * @author Arne Brachhold
  405. * @return int The calculated priority
  406. */
  407. function GetPostPriority($postID,$commentCount) {
  408. return 0;
  409. }
  410. }
  411. /**
  412. * Priority Provider which calculates the priority based on the number of comments
  413. * @author Arne Brachhold
  414. * @package sitemap
  415. * @since 3.0
  416. */
  417. class GoogleSitemapGeneratorPrioByCountProvider extends GoogleSitemapGeneratorPrioProviderBase {
  418. /**
  419. * Returns the (translated) name of this priority provider
  420. *
  421. * @since 3.0
  422. * @access public
  423. * @author Arne Brachhold
  424. * @return string The translated name
  425. */
  426. function GetName() {
  427. return __("Comment Count",'sitemap');
  428. }
  429. /**
  430. * Returns the (translated) description of this priority provider
  431. *
  432. * @since 3.0
  433. * @access public
  434. * @author Arne Brachhold
  435. * @return string The translated description
  436. */
  437. function GetDescription() {
  438. return __("Uses the number of comments of the post to calculate the priority",'sitemap');
  439. }
  440. /**
  441. * Initializes a new priority provider which calculates the post priority based on the number of comments
  442. *
  443. * @param $totalComments int The total number of comments of all posts
  444. * @param $totalPosts int The total number of posts
  445. * @since 3.0
  446. * @access public
  447. * @author Arne Brachhold
  448. */
  449. function GoogleSitemapGeneratorPrioByCountProvider($totalComments,$totalPosts) {
  450. parent::GoogleSitemapGeneratorPrioProviderBase($totalComments,$totalPosts);
  451. }
  452. /**
  453. * Returns the priority for a specified post
  454. *
  455. * @param $postID int The ID of the post
  456. * @param $commentCount int The number of comments for this post
  457. * @since 3.0
  458. * @access public
  459. * @author Arne Brachhold
  460. * @return int The calculated priority
  461. */
  462. function GetPostPriority($postID,$commentCount) {
  463. $prio=0;
  464. if($this->_totalComments>0 && $commentCount>0) {
  465. $prio = round(($commentCount*100/$this->_totalComments)/100,1);
  466. } else {
  467. $prio = 0;
  468. }
  469. return $prio;
  470. }
  471. }
  472. /**
  473. * Priority Provider which calculates the priority based on the average number of comments
  474. * @author Arne Brachhold
  475. * @package sitemap
  476. * @since 3.0
  477. */
  478. class GoogleSitemapGeneratorPrioByAverageProvider extends GoogleSitemapGeneratorPrioProviderBase {
  479. /**
  480. * @var int $_average The average number of comments per post
  481. * @access protected
  482. */
  483. var $_average=0.0;
  484. /**
  485. * Returns the (translated) name of this priority provider
  486. *
  487. * @since 3.0
  488. * @access public
  489. * @author Arne Brachhold
  490. * @return string The translated name
  491. */
  492. function GetName() {
  493. return __("Comment Average",'sitemap');
  494. }
  495. /**
  496. * Returns the (translated) description of this priority provider
  497. *
  498. * @since 3.0
  499. * @access public
  500. * @author Arne Brachhold
  501. * @return string The translated description
  502. */
  503. function GetDescription() {
  504. return __("Uses the average comment count to calculate the priority",'sitemap');
  505. }
  506. /**
  507. * Initializes a new priority provider which calculates the post priority based on the average number of comments
  508. *
  509. * @param $totalComments int The total number of comments of all posts
  510. * @param $totalPosts int The total number of posts
  511. * @since 3.0
  512. * @access public
  513. * @author Arne Brachhold
  514. */
  515. function GoogleSitemapGeneratorPrioByAverageProvider($totalComments,$totalPosts) {
  516. parent::GoogleSitemapGeneratorPrioProviderBase($totalComments,$totalPosts);
  517. if($this->_totalComments>0 && $this->_totalPosts>0) {
  518. $this->_average= (double) $this->_totalComments / $this->_totalPosts;
  519. }
  520. }
  521. /**
  522. * Returns the priority for a specified post
  523. *
  524. * @param $postID int The ID of the post
  525. * @param $commentCount int The number of comments for this post
  526. * @since 3.0
  527. * @access public
  528. * @author Arne Brachhold
  529. * @return int The calculated priority
  530. */
  531. function GetPostPriority($postID,$commentCount) {
  532. $prio = 0;
  533. //Do not divide by zero!
  534. if($this->_average==0) {
  535. if($commentCount>0) $prio = 1;
  536. else $prio = 0;
  537. } else {
  538. $prio = $commentCount/$this->_average;
  539. if($prio>1) $prio = 1;
  540. else if($prio<0) $prio = 0;
  541. }
  542. return round($prio,1);
  543. }
  544. }
  545. /**
  546. * Priority Provider which calculates the priority based on the popularity by the PopularityContest Plugin
  547. * @author Arne Brachhold
  548. * @package sitemap
  549. * @since 3.0
  550. */
  551. class GoogleSitemapGeneratorPrioByPopularityContestProvider extends GoogleSitemapGeneratorPrioProviderBase {
  552. /**
  553. * Returns the (translated) name of this priority provider
  554. *
  555. * @since 3.0
  556. * @access public
  557. * @author Arne Brachhold
  558. * @return string The translated name
  559. */
  560. function GetName() {
  561. return __("Popularity Contest",'sitemap');
  562. }
  563. /**
  564. * Returns the (translated) description of this priority provider
  565. *
  566. * @since 3.0
  567. * @access public
  568. * @author Arne Brachhold
  569. * @return string The translated description
  570. */
  571. function GetDescription() {
  572. return str_replace("%4","index.php?page=popularity-contest.php",str_replace("%3","options-general.php?page=popularity-contest.php",str_replace("%2","http://www.alexking.org/",str_replace("%1","http://www.alexking.org/index.php?content=software/wordpress/content.php",__("Uses the activated <a href=\"%1\">Popularity Contest Plugin</a> from <a href=\"%2\">Alex King</a>. See <a href=\"%3\">Settings</a> and <a href=\"%4\">Most Popular Posts</a>",'sitemap')))));
  573. }
  574. /**
  575. * Initializes a new priority provider which calculates the post priority based on the popularity by the PopularityContest Plugin
  576. *
  577. * @param $totalComments int The total number of comments of all posts
  578. * @param $totalPosts int The total number of posts
  579. * @since 3.0
  580. * @access public
  581. * @author Arne Brachhold
  582. */
  583. function GoogleSitemapGeneratorPrioByPopularityContestProvider($totalComments,$totalPosts) {
  584. parent::GoogleSitemapGeneratorPrioProviderBase($totalComments,$totalPosts);
  585. }
  586. /**
  587. * Returns the priority for a specified post
  588. *
  589. * @param $postID int The ID of the post
  590. * @param $commentCount int The number of comments for this post
  591. * @since 3.0
  592. * @access public
  593. * @author Arne Brachhold
  594. * @return int The calculated priority
  595. */
  596. function GetPostPriority($postID,$commentCount) {
  597. //$akpc is the global instance of the Popularity Contest Plugin
  598. global $akpc,$posts;
  599. $res=0;
  600. //Better check if its there
  601. if(!empty($akpc) && is_object($akpc)) {
  602. //Is the method we rely on available?
  603. if(method_exists($akpc,"get_post_rank")) {
  604. if(!is_array($posts) || !$posts) $posts = array();
  605. if(!isset($posts[$postID])) $posts[$postID] = get_post($postID);
  606. //popresult comes as a percent value
  607. $popresult=$akpc->get_post_rank($postID);
  608. if(!empty($popresult) && strpos($popresult,"%")!==false) {
  609. //We need to parse it to get the priority as an int (percent)
  610. $matches=null;
  611. preg_match("/([0-9]{1,3})\%/si",$popresult,$matches);
  612. if(!empty($matches) && is_array($matches) && count($matches)==2) {
  613. //Divide it so 100% = 1, 10% = 0.1
  614. $res=round(intval($matches[1])/100,1);
  615. }
  616. }
  617. }
  618. }
  619. return $res;
  620. }
  621. }
  622. /**
  623. * Class to generate a sitemaps.org Sitemaps compliant sitemap of a WordPress blog.
  624. *
  625. * @package sitemap
  626. * @author Arne Brachhold
  627. * @since 3.0
  628. */
  629. class GoogleSitemapGenerator {
  630. /**
  631. * @var Version of the generator in SVN
  632. */
  633. var $_svnVersion = '$Id: sitemap-core.php 224413 2010-04-02 15:54:52Z arnee $';
  634. /**
  635. * @var array The unserialized array with the stored options
  636. */
  637. var $_options = array();
  638. /**
  639. * @var array The saved additional pages
  640. */
  641. var $_pages = array();
  642. /**
  643. * @var array The values and names of the change frequencies
  644. */
  645. var $_freqNames = array();
  646. /**
  647. * @var array A list of class names which my be called for priority calculation
  648. */
  649. var $_prioProviders = array();
  650. /**
  651. * @var bool True if init complete (options loaded etc)
  652. */
  653. var $_initiated = false;
  654. /**
  655. * @var string Holds the last error if one occurs when writing the files
  656. */
  657. var $_lastError=null;
  658. /**
  659. * @var int The last handled post ID
  660. */
  661. var $_lastPostID = 0;
  662. /**
  663. * @var bool Defines if the sitemap building process is active at the moment
  664. */
  665. var $_isActive = false;
  666. /**
  667. * @var bool Defines if the sitemap building process has been scheduled via Wp cron
  668. */
  669. var $_isScheduled = false;
  670. /**
  671. * @var object The file handle which is used to write the sitemap file
  672. */
  673. var $_fileHandle = null;
  674. /**
  675. * @var object The file handle which is used to write the zipped sitemap file
  676. */
  677. var $_fileZipHandle = null;
  678. /**
  679. * Holds the user interface object
  680. *
  681. * @since 3.1.1
  682. * @var GoogleSitemapGeneratorUI
  683. */
  684. var $_ui = null;
  685. /**
  686. * Returns the path to the blog directory
  687. *
  688. * @since 3.0
  689. * @access private
  690. * @author Arne Brachhold
  691. * @return string The full path to the blog directory
  692. */
  693. function GetHomePath() {
  694. $res="";
  695. //Check if we are in the admin area -> get_home_path() is avaiable
  696. if(function_exists("get_home_path")) {
  697. $res = get_home_path();
  698. } else {
  699. //get_home_path() is not available, but we can't include the admin
  700. //libraries because many plugins check for the "check_admin_referer"
  701. //function to detect if you are on an admin page. So we have to copy
  702. //the get_home_path function in our own...
  703. $home = get_option( 'home' );
  704. if ( $home != '' && $home != get_option( 'siteurl' ) ) {
  705. $home_path = parse_url( $home );
  706. $home_path = $home_path['path'];
  707. $root = str_replace( $_SERVER["PHP_SELF"], '', $_SERVER["SCRIPT_FILENAME"] );
  708. $home_path = trailingslashit( $root.$home_path );
  709. } else {
  710. $home_path = ABSPATH;
  711. }
  712. $res = $home_path;
  713. }
  714. return $res;
  715. }
  716. /**
  717. * Returns the path to the directory where the plugin file is located
  718. * @since 3.0b5
  719. * @access private
  720. * @author Arne Brachhold
  721. * @return string The path to the plugin directory
  722. */
  723. function GetPluginPath() {
  724. $path = dirname(__FILE__);
  725. return trailingslashit(str_replace("\\","/",$path));
  726. }
  727. /**
  728. * Returns the URL to the directory where the plugin file is located
  729. * @since 3.0b5
  730. * @access private
  731. * @author Arne Brachhold
  732. * @return string The URL to the plugin directory
  733. */
  734. function GetPluginUrl() {
  735. //Try to use WP API if possible, introduced in WP 2.6
  736. if (function_exists('plugins_url')) return trailingslashit(plugins_url(basename(dirname(__FILE__))));
  737. //Try to find manually... can't work if wp-content was renamed or is redirected
  738. $path = dirname(__FILE__);
  739. $path = str_replace("\\","/",$path);
  740. $path = trailingslashit(get_bloginfo('wpurl')) . trailingslashit(substr($path,strpos($path,"wp-content/")));
  741. return $path;
  742. }
  743. /**
  744. * Returns the URL to default XSLT style if it exists
  745. * @since 3.0b5
  746. * @access private
  747. * @author Arne Brachhold
  748. * @return string The URL to the default stylesheet, empty string if not available.
  749. */
  750. function GetDefaultStyle() {
  751. $p = $this->GetPluginPath();
  752. if(file_exists($p . "sitemap.xsl")) {
  753. $url = $this->GetPluginUrl();
  754. //If called over the admin area using HTTPS, the stylesheet would also be https url, even if the blog frontend is not.
  755. if(substr(get_bloginfo('url'),0,5) !="https" && substr($url,0,5)=="https") $url="http" . substr($url,5);
  756. return $url . 'sitemap.xsl';
  757. }
  758. return '';
  759. }
  760. /**
  761. * Sets up the default configuration
  762. *
  763. * @since 3.0
  764. * @access private
  765. * @author Arne Brachhold
  766. */
  767. function InitOptions() {
  768. $this->_options=array();
  769. $this->_options["sm_b_prio_provider"]="GoogleSitemapGeneratorPrioByCountProvider"; //Provider for automatic priority calculation
  770. $this->_options["sm_b_filename"]="sitemap.xml"; //Name of the Sitemap file
  771. $this->_options["sm_b_debug"]=true; //Write debug messages in the xml file
  772. $this->_options["sm_b_xml"]=true; //Create a .xml file
  773. $this->_options["sm_b_gzip"]=true; //Create a gzipped .xml file(.gz) file
  774. $this->_options["sm_b_ping"]=true; //Auto ping Google
  775. $this->_options["sm_b_pingyahoo"]=false; //Auto ping YAHOO
  776. $this->_options["sm_b_yahookey"]=''; //YAHOO Application Key
  777. $this->_options["sm_b_pingask"]=true; //Auto ping Ask.com
  778. $this->_options["sm_b_pingmsn"]=true; //Auto ping MSN
  779. $this->_options["sm_b_manual_enabled"]=false; //Allow manual creation of the sitemap via GET request
  780. $this->_options["sm_b_auto_enabled"]=true; //Rebuild sitemap when content is changed
  781. $this->_options["sm_b_auto_delay"]=true; //Use WP Cron to execute the building process in the background
  782. $this->_options["sm_b_manual_key"]=md5(microtime());//The secret key to build the sitemap via GET request
  783. $this->_options["sm_b_memory"] = ''; //Set Memory Limit (e.g. 16M)
  784. $this->_options["sm_b_time"] = -1; //Set time limit in seconds, 0 for unlimited, -1 for disabled
  785. $this->_options["sm_b_max_posts"] = -1; //Maximum number of posts, <= 0 for all
  786. $this->_options["sm_b_safemode"] = false; //Enable MySQL Safe Mode (doesn't use unbuffered results)
  787. $this->_options["sm_b_style_default"] = true; //Use default style
  788. $this->_options["sm_b_style"] = ''; //Include a stylesheet in the XML
  789. $this->_options["sm_b_robots"] = true; //Add sitemap location to WordPress' virtual robots.txt file
  790. $this->_options["sm_b_exclude"] = array(); //List of post / page IDs to exclude
  791. $this->_options["sm_b_exclude_cats"] = array(); //List of post / page IDs to exclude
  792. $this->_options["sm_b_location_mode"]="auto"; //Mode of location, auto or manual
  793. $this->_options["sm_b_filename_manual"]=""; //Manuel filename
  794. $this->_options["sm_b_fileurl_manual"]=""; //Manuel fileurl
  795. $this->_options["sm_in_home"]=true; //Include homepage
  796. $this->_options["sm_in_posts"]=true; //Include posts
  797. $this->_options["sm_in_posts_sub"]=false; //Include post pages (<!--nextpage--> tag)
  798. $this->_options["sm_in_pages"]=true; //Include static pages
  799. $this->_options["sm_in_cats"]=false; //Include categories
  800. $this->_options["sm_in_arch"]=false; //Include archives
  801. $this->_options["sm_in_auth"]=false; //Include author pages
  802. $this->_options["sm_in_tags"]=false; //Include tag pages
  803. $this->_options["sm_in_tax"]=array(); //Include additional taxonomies
  804. $this->_options["sm_in_lastmod"]=true; //Include the last modification date
  805. $this->_options["sm_cf_home"]="daily"; //Change frequency of the homepage
  806. $this->_options["sm_cf_posts"]="monthly"; //Change frequency of posts
  807. $this->_options["sm_cf_pages"]="weekly"; //Change frequency of static pages
  808. $this->_options["sm_cf_cats"]="weekly"; //Change frequency of categories
  809. $this->_options["sm_cf_auth"]="weekly"; //Change frequency of author pages
  810. $this->_options["sm_cf_arch_curr"]="daily"; //Change frequency of the current archive (this month)
  811. $this->_options["sm_cf_arch_old"]="yearly"; //Change frequency of older archives
  812. $this->_options["sm_cf_tags"]="weekly"; //Change frequency of tags
  813. $this->_options["sm_pr_home"]=1.0; //Priority of the homepage
  814. $this->_options["sm_pr_posts"]=0.6; //Priority of posts (if auto prio is disabled)
  815. $this->_options["sm_pr_posts_min"]=0.2; //Minimum Priority of posts, even if autocalc is enabled
  816. $this->_options["sm_pr_pages"]=0.6; //Priority of static pages
  817. $this->_options["sm_pr_cats"]=0.3; //Priority of categories
  818. $this->_options["sm_pr_arch"]=0.3; //Priority of archives
  819. $this->_options["sm_pr_auth"]=0.3; //Priority of author pages
  820. $this->_options["sm_pr_tags"]=0.3; //Priority of tags
  821. $this->_options["sm_i_donated"]=false; //Did you donate? Thank you! :)
  822. $this->_options["sm_i_hide_donated"]=false; //And hide the thank you..
  823. $this->_options["sm_i_install_date"]=time(); //The installation date
  824. $this->_options["sm_i_hide_note"]=false; //Hide the note which appears after 30 days
  825. $this->_options["sm_i_hide_works"]=false; //Hide the "works?" message which appears after 15 days
  826. $this->_options["sm_i_hide_donors"]=false; //Hide the list of donations
  827. }
  828. /**
  829. * Loads the configuration from the database
  830. *
  831. * @since 3.0
  832. * @access private
  833. * @author Arne Brachhold
  834. */
  835. function LoadOptions() {
  836. $this->InitOptions();
  837. //First init default values, then overwrite it with stored values so we can add default
  838. //values with an update which get stored by the next edit.
  839. $storedoptions=get_option("sm_options");
  840. if($storedoptions && is_array($storedoptions)) {
  841. foreach($storedoptions AS $k=>$v) {
  842. $this->_options[$k]=$v;
  843. }
  844. } else update_option("sm_options",$this->_options); //First time use, store default values
  845. }
  846. /**
  847. * Initializes a new Google Sitemap Generator
  848. *
  849. * @since 3.0
  850. * @access private
  851. * @author Arne Brachhold
  852. */
  853. function GoogleSitemapGenerator() {
  854. }
  855. /**
  856. * Returns the version of the generator
  857. *
  858. * @since 3.0
  859. * @access public
  860. * @author Arne Brachhold
  861. * @return int The version
  862. */
  863. function GetVersion() {
  864. return GoogleSitemapGeneratorLoader::GetVersion();
  865. }
  866. /**
  867. * Returns all parent classes of a class
  868. *
  869. * @param $className string The name of the class
  870. *
  871. * @since 3.0
  872. * @access private
  873. * @author Arne Brachhold
  874. * @return array An array which contains the names of the parent classes
  875. */
  876. function GetParentClasses($classname) {
  877. $parent = get_parent_class($classname);
  878. $parents = array();
  879. if (!empty($parent)) {
  880. $parents = $this->GetParentClasses($parent);
  881. $parents[] = strtolower($parent);
  882. }
  883. return $parents;
  884. }
  885. /**
  886. * Returns if a class is a subclass of another class
  887. *
  888. * @param $className string The name of the class
  889. * @param $$parentName string The name of the parent class
  890. *
  891. * @since 3.0
  892. * @access private
  893. * @author Arne Brachhold
  894. * @return bool true if the given class is a subclass of the other one
  895. */
  896. function IsSubclassOf($className, $parentName) {
  897. $className = strtolower($className);
  898. $parentName = strtolower($parentName);
  899. if(empty($className) || empty($parentName) || !class_exists($className) || !class_exists($parentName)) return false;
  900. $parents=$this->GetParentClasses($className);
  901. return in_array($parentName,$parents);
  902. }
  903. /**
  904. * Loads up the configuration and validates the prioity providers
  905. *
  906. * This method is only called if the sitemaps needs to be build or the admin page is displayed.
  907. *
  908. * @since 3.0
  909. * @access private
  910. * @author Arne Brachhold
  911. */
  912. function Initate() {
  913. if(!$this->_initiated) {
  914. //Loading language file...
  915. //load_plugin_textdomain('sitemap');
  916. //Hmm, doesn't work if the plugin file has its own directory.
  917. //Let's make it our way... load_plugin_textdomain() searches only in the wp-content/plugins dir.
  918. $currentLocale = get_locale();
  919. if(!empty($currentLocale)) {
  920. $moFile = dirname(__FILE__) . "/lang/sitemap-" . $currentLocale . ".mo";
  921. if(@file_exists($moFile) && is_readable($moFile)) load_textdomain('sitemap', $moFile);
  922. }
  923. $this->_freqNames = array(
  924. "always"=>__("Always","sitemap"),
  925. "hourly"=>__("Hourly","sitemap"),
  926. "daily"=>__("Daily","sitemap"),
  927. "weekly"=>__("Weekly","sitemap"),
  928. "monthly"=>__("Monthly","sitemap"),
  929. "yearly"=>__("Yearly","sitemap"),
  930. "never"=>__("Never","sitemap")
  931. );
  932. $this->LoadOptions();
  933. $this->LoadPages();
  934. //Register our own priority providers
  935. add_filter("sm_add_prio_provider",array(&$this, 'AddDefaultPrioProviders'));
  936. //Let other plugins register their providers
  937. $r = apply_filters("sm_add_prio_provider",$this->_prioProviders);
  938. //Check if no plugin return null
  939. if($r != null) $this->_prioProviders = $r;
  940. $this->ValidatePrioProviders();
  941. $this->_initiated = true;
  942. }
  943. }
  944. /**
  945. * Returns the instance of the Sitemap Generator
  946. *
  947. * @since 3.0
  948. * @access public
  949. * @return GoogleSitemapGenerator The instance or null if not available.
  950. * @author Arne Brachhold
  951. */
  952. function &GetInstance() {
  953. if(isset($GLOBALS["sm_instance"])) {
  954. return $GLOBALS["sm_instance"];
  955. } else return null;
  956. }
  957. /**
  958. * Returns if the sitemap building process is currently active
  959. *
  960. * @since 3.0
  961. * @access public
  962. * @return bool true if active
  963. * @author Arne Brachhold
  964. */
  965. function IsActive() {
  966. $inst = &GoogleSitemapGenerator::GetInstance();
  967. return ($inst != null && $inst->_isActive);
  968. }
  969. /**
  970. * Returns if the compressed sitemap was activated
  971. *
  972. * @since 3.0b8
  973. * @access private
  974. * @author Arne Brachhold
  975. * @return true if compressed
  976. */
  977. function IsGzipEnabled() {
  978. return ($this->GetOption("b_gzip")===true && function_exists("gzwrite"));
  979. }
  980. /**
  981. * Returns if this version of WordPress supports the new taxonomy system
  982. *
  983. * @since 3.0b8
  984. * @access private
  985. * @author Arne Brachhold
  986. * @return true if supported
  987. */
  988. function IsTaxonomySupported() {
  989. return (function_exists("get_taxonomy") && function_exists("get_terms"));
  990. }
  991. /**
  992. * Returns the list of custom taxonies. These are basically all taxonomies without categories and post tags
  993. *
  994. * @since 3.1.7
  995. * @return array Array of names of user-defined taxonomies
  996. */
  997. function GetCustomTaxonomies() {
  998. $taxonomies = get_object_taxonomies('post');
  999. return array_diff($taxonomies,array("category","post_tag"));
  1000. }
  1001. /**
  1002. * Enables the Google Sitemap Generator and registers the WordPress hooks
  1003. *
  1004. * @since 3.0
  1005. * @access public
  1006. * @author Arne Brachhold
  1007. */
  1008. function Enable() {
  1009. if(!isset($GLOBALS["sm_instance"])) {
  1010. $GLOBALS["sm_instance"]=new GoogleSitemapGenerator();
  1011. }
  1012. }
  1013. /**
  1014. * Checks if sitemap building after content changed is enabled and rebuild the sitemap
  1015. *
  1016. * @param int $postID The ID of the post to handle. Used to avoid double rebuilding if more than one hook was fired.
  1017. * @param bool $external Added in 3.1.9. Skips checking of b_auto_enabled if set to true
  1018. * @since 3.0
  1019. * @access public
  1020. * @author Arne Brachhold
  1021. */
  1022. function CheckForAutoBuild($postID, $external = false) {
  1023. global $wp_version;
  1024. $this->Initate();
  1025. //Build one time per post and if not importing.
  1026. if((($this->GetOption("b_auto_enabled")===true && $this->_lastPostID != $postID) || $external) && (!defined('WP_IMPORTING') || WP_IMPORTING != true)) {
  1027. //Build the sitemap directly or schedule it with WP cron
  1028. if($this->GetOption("b_auto_delay")==true && floatval($wp_version) >= 2.1) {
  1029. if(!$this->_isScheduled) {
  1030. //Schedule in 15 seconds, this should be enough to catch all changes.
  1031. //Clear all other existing hooks, so the sitemap is only built once.
  1032. wp_clear_scheduled_hook('sm_build_cron');
  1033. wp_schedule_single_event(time()+15,'sm_build_cron');
  1034. $this->_isScheduled = true;
  1035. }
  1036. } else {
  1037. //Build sitemap only once and never in bulk mode
  1038. if(!$this->_lastPostID && (!isset($_GET["delete"]) || count((array) $_GET['delete'])<=0)) {
  1039. $this->BuildSitemap();
  1040. }
  1041. }
  1042. $this->_lastPostID = $postID;
  1043. }
  1044. }
  1045. /**
  1046. * Builds the sitemap by external request, for example other plugins.
  1047. *
  1048. * @since 3.1.9
  1049. * @return null
  1050. */
  1051. function BuildNowRequest() {
  1052. $this->CheckForAutoBuild(null, true);
  1053. }
  1054. /**
  1055. * Checks if the rebuild request was send and starts to rebuilt the sitemap
  1056. *
  1057. * @since 3.0
  1058. * @access public
  1059. * @author Arne Brachhold
  1060. */
  1061. function CheckForManualBuild() {
  1062. if(!empty($_GET["sm_command"]) && !empty($_GET["sm_key"])) {
  1063. $this->Initate();
  1064. if($this->GetOption("b_manual_enabled")===true && $_GET["sm_command"]=="build" && $_GET["sm_key"]==$this->GetOption("b_manual_key")) {
  1065. $this->BuildSitemap();
  1066. echo "DONE";
  1067. exit;
  1068. }
  1069. }
  1070. }
  1071. /**
  1072. * Validates all given Priority Providers by checking them for required methods and existence
  1073. *
  1074. * @since 3.0
  1075. * @access private
  1076. * @author Arne Brachhold
  1077. */
  1078. function ValidatePrioProviders() {
  1079. $validProviders=array();
  1080. for($i=0; $i<count($this->_prioProviders); $i++) {
  1081. if(class_exists($this->_prioProviders[$i])) {
  1082. if($this->IsSubclassOf($this->_prioProviders[$i],"GoogleSitemapGeneratorPrioProviderBase")) {
  1083. array_push($validProviders,$this->_prioProviders[$i]);
  1084. }
  1085. }
  1086. }
  1087. $this->_prioProviders=$validProviders;
  1088. if(!$this->GetOption("b_prio_provider")) {
  1089. if(!in_array($this->GetOption("b_prio_provider"),$this->_prioProviders,true)) {
  1090. $this->SetOption("b_prio_provider","");
  1091. }
  1092. }
  1093. }
  1094. /**
  1095. * Adds the default Priority Providers to the provider list
  1096. *
  1097. * @since 3.0
  1098. * @access private
  1099. * @author Arne Brachhold
  1100. */
  1101. function AddDefaultPrioProviders($providers) {
  1102. array_push($providers,"GoogleSitemapGeneratorPrioByCountProvider");
  1103. array_push($providers,"GoogleSitemapGeneratorPrioByAverageProvider");
  1104. if(class_exists("ak_popularity_contest")) {
  1105. array_push($providers,"GoogleSitemapGeneratorPrioByPopularityContestProvider");
  1106. }
  1107. return $providers;
  1108. }
  1109. /**
  1110. * Loads the stored pages from the database
  1111. *
  1112. * @since 3.0
  1113. * @access private
  1114. * @author Arne Brachhold
  1115. */
  1116. function LoadPages() {
  1117. global $wpdb;
  1118. $needsUpdate=false;
  1119. $pagesString=$wpdb->get_var("SELECT option_value FROM $wpdb->options WHERE option_name = 'sm_cpages'");
  1120. //Class sm_page was renamed with 3.0 -> rename it in serialized value for compatibility
  1121. if(!empty($pagesString) && strpos($pagesString,"sm_page")!==false) {
  1122. $pagesString = str_replace("O:7:\"sm_page\"","O:26:\"GoogleSitemapGeneratorPage\"",$pagesString);
  1123. $needsUpdate=true;
  1124. }
  1125. if(!empty($pagesString)) {
  1126. $storedpages=unserialize($pagesString);
  1127. $this->_pages=$storedpages;
  1128. } else {
  1129. $this->_pages=array();
  1130. }
  1131. if($needsUpdate) $this->SavePages();
  1132. }
  1133. /**
  1134. * Saved the additional pages back to the database
  1135. *
  1136. * @since 3.0
  1137. * @access private
  1138. * @author Arne Brachhold
  1139. * @return true on success
  1140. */
  1141. function SavePages() {
  1142. $oldvalue = get_option("sm_cpages");
  1143. if($oldvalue == $this->_pages) {
  1144. return true;
  1145. } else {
  1146. delete_option("sm_cpages");
  1147. //Add the option, Note the autoload=false because when the autoload happens, our class GoogleSitemapGeneratorPage doesn't exist
  1148. add_option("sm_cpages",$this->_pages,"Storage for custom pages of the sitemap plugin","no");
  1149. return true;
  1150. }
  1151. }
  1152. /**
  1153. * Returns the URL for the sitemap file
  1154. *
  1155. * @since 3.0
  1156. * @access private
  1157. * @author Arne Brachhold
  1158. * @param bool $forceAuto Force the return value to the autodetected value.
  1159. * @return The URL to the Sitemap file
  1160. */
  1161. function GetXmlUrl($forceAuto=false) {
  1162. if(!$forceAuto && $this->GetOption("b_location_mode")=="manual") {
  1163. return $this->GetOption("b_fileurl_manual");
  1164. } else {
  1165. return trailingslashit(get_bloginfo('siteurl')). $this->GetOption("b_filename");
  1166. }
  1167. }
  1168. /**
  1169. * Returns the URL for the gzipped sitemap file
  1170. *
  1171. * @since 3.0
  1172. * @access private
  1173. * @author Arne Brachhold
  1174. * @param bool $forceAuto Force the return value to the autodetected value.
  1175. * @return The URL to the gzipped Sitemap file
  1176. */
  1177. function GetZipUrl($forceAuto=false) {
  1178. return $this->GetXmlUrl($forceAuto) . ".gz";
  1179. }
  1180. /**
  1181. * Returns the file system path to the sitemap file
  1182. *
  1183. * @since 3.0
  1184. * @access private
  1185. * @author Arne Brachhold
  1186. * @param bool $forceAuto Force the return value to the autodetected value.
  1187. * @return The file system path;
  1188. */
  1189. function GetXmlPath($forceAuto=false) {
  1190. if(!$forceAuto && $this->GetOption("b_location_mode")=="manual") {
  1191. return $this->GetOption("b_filename_manual");
  1192. } else {
  1193. return $this->GetHomePath() . $this->GetOption("b_filename");
  1194. }
  1195. }
  1196. /**
  1197. * Returns the file system path to the gzipped sitemap file
  1198. *
  1199. * @since 3.0
  1200. * @access private
  1201. * @author Arne Brachhold
  1202. * @param bool $forceAuto Force the return value to the autodetected value.
  1203. * @return The file system path;
  1204. */
  1205. function GetZipPath($forceAuto=false) {
  1206. return $this->GetXmlPath($forceAuto) . ".gz";
  1207. }
  1208. /**
  1209. * Returns the option value for the given key
  1210. *
  1211. * @since 3.0
  1212. * @access private
  1213. * @author Arne Brachhold
  1214. * @param $key string The Configuration Key
  1215. * @return mixed The value
  1216. */
  1217. function GetOption($key) {
  1218. $key="sm_" . $key;
  1219. if(array_key_exists($key,$this->_options)) {
  1220. return $this->_options[$key];
  1221. } else return null;
  1222. }
  1223. /**
  1224. * Sets an option to a new value
  1225. *
  1226. * @since 3.0
  1227. * @access private
  1228. * @author Arne Brachhold
  1229. * @param $key string The configuration key
  1230. * @param $value mixed The new object
  1231. */
  1232. function SetOption($key,$value) {
  1233. if(strstr($key,"sm_")!==0) $key="sm_" . $key;
  1234. $this->_options[$key]=$value;
  1235. }
  1236. /**
  1237. * Saves the options back to the database
  1238. *
  1239. * @since 3.0
  1240. * @access private
  1241. * @author Arne Brachhold
  1242. * @return bool true on success
  1243. */
  1244. function SaveOptions() {
  1245. $oldvalue = get_option("sm_options");
  1246. if($oldvalue == $this->_options) {
  1247. return true;
  1248. } else return update_option("sm_options",$this->_options);
  1249. }
  1250. /**
  1251. * Retrieves the number of comments of a post in a asso. array
  1252. * The key is the postID, the value the number of comments
  1253. *
  1254. * @since 3.0
  1255. * @access private
  1256. * @author Arne Brachhold
  1257. * @return array An array with postIDs and their comment count
  1258. */
  1259. function GetComments() {
  1260. global $wpdb;
  1261. $comments=array();
  1262. //Query comments and add them into the array
  1263. $commentRes=$wpdb->get_results("SELECT `comment_post_ID` as `post_id`, COUNT(comment_ID) as `comment_count` FROM `" . $wpdb->comments . "` WHERE `comment_approved`='1' GROUP BY `comment_post_ID`");
  1264. if($commentRes) {
  1265. foreach($commentRes as $comment) {
  1266. $comments[$comment->post_id]=$comment->comment_count;
  1267. }
  1268. }
  1269. return $comments;
  1270. }
  1271. /**
  1272. * Calculates the full number of comments from an sm_getComments() generated array
  1273. *
  1274. * @since 3.0
  1275. * @access private
  1276. * @author Arne Brachhold
  1277. * @param $comments array The Array with posts and c0mment count
  1278. * @see sm_getComments
  1279. * @return The full number of comments
  1280. */
  1281. function GetCommentCount($comments) {
  1282. $commentCount=0;
  1283. foreach($comments AS $k=>$v) {
  1284. $commentCount+=$v;
  1285. }
  1286. return $commentCount;
  1287. }
  1288. /**
  1289. * Adds a url to the sitemap. You can use this method or call AddElement directly.
  1290. *
  1291. * @since 3.0
  1292. * @access public
  1293. * @author Arne Brachhold
  1294. * @param $loc string The location (url) of the page
  1295. * @param $lastMod int The last Modification time as a UNIX timestamp
  1296. * @param $changeFreq string The change frequenty of the page, Valid values are "always", "hourly", "daily", "weekly", "monthly", "yearly" and "never".
  1297. * @param $priorty float The priority of the page, between 0.0 and 1.0
  1298. * @see AddElement
  1299. * @return string The URL node
  1300. */
  1301. function AddUrl($loc, $lastMod = 0, $changeFreq = "monthly", $priority = 0.5) {
  1302. //Strip out the last modification time if activated
  1303. if($this->GetOption('in_lastmod')===false) $lastMod = 0;
  1304. $page = new GoogleSitemapGeneratorPage($loc, $priority, $changeFreq, $lastMod);
  1305. $this->AddElement($page);
  1306. }
  1307. /**
  1308. * Adds an element to the sitemap
  1309. *
  1310. * @since 3.0
  1311. * @access private
  1312. * @author Arne Brachhold
  1313. * @param $page The element
  1314. */
  1315. function AddElement(&$page) {
  1316. if(empty($page)) return;
  1317. $s = $page->Render();
  1318. if($this->_fileZipHandle && $this->IsGzipEnabled()) {
  1319. gzwrite($this->_fileZipHandle,$s);
  1320. }
  1321. if($this->_fileHandle && $this->GetOption("b_xml")) {
  1322. fwrite($this->_fileHandle,$s);
  1323. }
  1324. }
  1325. /**
  1326. * Checks if a file is writable and tries to make it if not.
  1327. *
  1328. * @since 3.05b
  1329. * @access private
  1330. * @author VJTD3 <http://www.VJTD3.com>
  1331. * @return bool true if writable
  1332. */
  1333. function IsFileWritable($filename) {
  1334. //can we write?
  1335. if(!is_writable($filename)) {
  1336. //no we can't.
  1337. if(!@chmod($filename, 0666)) {
  1338. $pathtofilename = dirname($filename);
  1339. //Lets check if parent directory is writable.
  1340. if(!is_writable($pathtofilename)) {
  1341. //it's not writeable too.
  1342. if(!@chmod($pathtoffilename, 0666)) {
  1343. //darn couldn't fix up parrent directory this hosting is foobar.
  1344. //Lets error because of the permissions problems.
  1345. return false;
  1346. }
  1347. }
  1348. }
  1349. }
  1350. //we can write, return 1/true/happy dance.
  1351. return true;
  1352. }
  1353. /**
  1354. * Adds the sitemap to the virtual robots.txt file
  1355. * This function is executed by WordPress with the do_robots hook
  1356. *
  1357. * @since 3.1.2
  1358. */
  1359. function DoRobots() {
  1360. $this->Initate();
  1361. if($this->GetOption('b_robots') === true) {
  1362. $smUrl = $this->GetXmlUrl();
  1363. if($this->IsGzipEnabled()) {
  1364. $smUrl = $this->GetZipUrl();
  1365. }
  1366. echo "\nSitemap: " . $smUrl . "\n";
  1367. }
  1368. }
  1369. /**
  1370. * Builds the sitemap and writes it into a xml file.
  1371. *
  1372. * ATTENTION PLUGIN DEVELOPERS! DONT CALL THIS METHOD DIRECTLY!
  1373. * The method is probably not available, since it is only loaded when needed.
  1374. * Use do_action("sm_rebuild"); if you want to rebuild the sitemap.
  1375. * Please refer to the documentation.txt for more details.
  1376. *
  1377. * @since 3.0
  1378. * @access public
  1379. * @author Arne Brachhold <himself [at] arnebrachhold [dot] de>
  1380. * @return array An array with messages such as failed writes etc.
  1381. */
  1382. function BuildSitemap() {
  1383. global $wpdb, $posts, $wp_version;
  1384. $this->Initate();
  1385. if($this->GetOption("b_memory")!='') {
  1386. @ini_set("memory_limit",$this->GetOption("b_memory"));
  1387. }
  1388. if($this->GetOption("b_time")!=-1) {
  1389. @set_time_limit($this->GetOption("b_time"));
  1390. }
  1391. //This object saves the status information of the script directly to the database
  1392. $status = new GoogleSitemapGeneratorStatus();
  1393. //Other plugins can detect if the building process is active
  1394. $this->_isActive = true;
  1395. //$this->AddElement(new GoogleSitemapGeneratorXmlEntry());
  1396. //Debug mode?
  1397. $debug=$this->GetOption("b_debug");
  1398. if($this->GetOption("b_xml")) {
  1399. $fileName = $this->GetXmlPath();
  1400. $status->StartXml($this->GetXmlPath(),$this->GetXmlUrl());
  1401. if($this->IsFileWritable($fileName)) {
  1402. $this->_fileHandle = fopen($fileName,"w");
  1403. if(!$this->_fileHandle) $status->EndXml(false,"Not openable");
  1404. } else $status->EndXml(false,"not writable");
  1405. }
  1406. //Write gzipped sitemap file
  1407. if($this->IsGzipEnabled()) {
  1408. $fileName = $this->GetZipPath();
  1409. $status->StartZip($this->GetZipPath(),$this->GetZipUrl());
  1410. if($this->IsFileWritable($fileName)) {
  1411. $this->_fileZipHandle = gzopen($fileName,"w1");
  1412. if(!$this->_fileZipHandle) $status->EndZip(false,"Not openable");
  1413. } else $status->EndZip(false,"not writable");
  1414. }
  1415. if(!$this->_fileHandle && !$this->_fileZipHandle) {
  1416. $status->End();
  1417. return;
  1418. }
  1419. //Content of the XML file
  1420. $this->AddElement(new GoogleSitemapGeneratorXmlEntry('<?xml version="1.0" encoding="UTF-8"' . '?' . '>'));
  1421. $styleSheet = ($this->GetDefaultStyle() && $this->GetOption('b_style_default')===true?$this->GetDefaultStyle():$this->GetOption('b_style'));
  1422. if(!empty($styleSheet)) {
  1423. $this->AddElement(new GoogleSitemapGeneratorXmlEntry('<' . '?xml-stylesheet type="text/xsl" href="' . $styleSheet . '"?' . '>'));
  1424. }
  1425. $this->AddElement(new GoogleSitemapGeneratorDebugEntry("generator=\"wordpress/" . get_bloginfo('version') . "\""));
  1426. $this->AddElement(new GoogleSitemapGeneratorDebugEntry("sitemap-generator-url=\"http://www.arnebrachhold.de\" sitemap-generator-version=\"" . $this->GetVersion() . "\""));
  1427. $this->AddElement(new GoogleSitemapGeneratorDebugEntry("generated-on=\"" . date(get_option("date_format") . " " . get_option("time_format")) . "\""));
  1428. //All comments as an asso. Array (postID=>commentCount)
  1429. $comments=($this->GetOption("b_prio_provider")!=""?$this->GetComments():array());
  1430. //Full number of comments
  1431. $commentCount=(count($comments)>0?$this->GetCommentCount($comments):0);
  1432. if($debug && $this->GetOption("b_prio_provider")!="") {
  1433. $this->AddElement(new GoogleSitemapGeneratorDebugEntry("Debug: Total comment count: " . $commentCount));
  1434. }
  1435. //Go XML!
  1436. $this->AddElement(new GoogleSitemapGeneratorXmlEntry('<urlset xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd" xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">'));
  1437. $home = get_bloginfo('url');
  1438. $homePid = 0;
  1439. //Add the home page (WITH a slash!)
  1440. if($this->GetOption("in_home")) {
  1441. if('page' == get_option('show_on_front') && get_option('page_on_front')) {
  1442. $pageOnFront = get_option('page_on_front');
  1443. $p = get_page($pageOnFront);
  1444. if($p) {
  1445. $homePid = $p->ID;
  1446. $this->AddUrl(trailingslashit($home),$this->GetTimestampFromMySql(($p->post_modified_gmt && $p->post_modified_gmt!='0000-00-00 00:00:00'?$p->post_modified_gmt:$p->post_date_gmt)),$this->GetOption("cf_home"),$this->GetOption("pr_home"));
  1447. }
  1448. } else {
  1449. $this->AddUrl(trailingslashit($home),$this->GetTimestampFromMySql(get_lastpostmodified('GMT')),$this->GetOption("cf_home"),$this->GetOption("pr_home"));
  1450. }
  1451. }
  1452. //Add the posts
  1453. if($this->GetOption("in_posts") || $this->GetOption("in_pages")) {
  1454. if($debug) $this->AddElement(new GoogleSitemapGeneratorDebugEntry("Debug: Start Postings"));
  1455. //Pre 2.1 compatibility. 2.1 introduced 'future' as post_status so we don't need to check post_date
  1456. $wpCompat = (floatval($wp_version) < 2.1);
  1457. $useQTransLate = false; //function_exists('qtrans_convertURL') && function_exists('qtrans_getEnabledLanguages'); Not really working yet
  1458. $excludes = $this->GetOption('b_exclude'); //Excluded posts and pages (user enetered ID)
  1459. $exclCats = $this->GetOption("b_exclude_cats"); // Excluded cats
  1460. if($exclCats && count($exclCats)>0 && $this->IsTaxonomySupported()) {
  1461. $excludedCatPosts = get_objects_in_term($exclCats,"category"); // Get all posts in excl. cats. Unforttunately this also gives us pages, revisions and so on...
  1462. //Remove the pages, revisions etc from the exclude by category list, because they are always in the uncategorized one.
  1463. if(count($excludedCatPosts)>0) {
  1464. $exclPages = $wpdb->get_col("SELECT ID FROM `" . $wpdb->posts . "` WHERE post_type!='post' AND ID IN ('" . implode("','",$excludedCatPosts) . "')");
  1465. $exclPages = array_map('intval', $exclPages);
  1466. //Remove the pages from the exlusion list before
  1467. if(count($exclPages)>0) $excludedCatPosts = array_diff($excludedCatPosts, $exclPages);
  1468. //Merge the category exclusion list with the users one
  1469. if(count($excludedCatPosts)>0) $excludes = array_merge($excludes, $excludedCatPosts);
  1470. }
  1471. }
  1472. $contentStmt = '';
  1473. if($useQTransLate) {
  1474. $contentStmt.=', post_content ';
  1475. }
  1476. $postPageStmt = '';
  1477. $inSubPages = ($this->GetOption('in_posts_sub')===true);
  1478. if($inSubPages && $this->GetOption('in_posts')===true) {
  1479. $pageDivider='<!--nextpage-->';
  1480. $postPageStmt = ", (character_length(`post_content`) - character_length(REPLACE(`post_content`, '$pageDivider', ''))) / " . strlen($pageDivider) . " as postPages";
  1481. }
  1482. $sql="SELECT `ID`, `post_author`, `post_date`, `post_date_gmt`, `post_status`, `post_name`, `post_modified`, `post_modified_gmt`, `post_parent`, `post_type` $postPageStmt $contentStmt FROM `" . $wpdb->posts . "` WHERE ";
  1483. $where = '(';
  1484. if($this->GetOption('in_posts')) {
  1485. //WP < 2.1: posts are post_status = publish
  1486. //WP >= 2.1: post_type must be 'post', no date check required because future posts are post_status='future'
  1487. if($wpCompat) $where.="(post_status = 'publish' AND post_date_gmt <= '" . gmdate('Y-m-d H:i:59') . "')";
  1488. else $where.=" (post_status = 'publish' AND (post_type = 'post' OR post_type = '')) ";
  1489. }
  1490. if($this->GetOption('in_pages')) {
  1491. if($this->GetOption('in_posts')) {
  1492. $where.=" OR ";
  1493. }
  1494. if($wpCompat) {
  1495. //WP < 2.1: posts have post_status = published, pages have post_status = static
  1496. $where.=" post_status='static' ";
  1497. } else {
  1498. //WP >= 2.1: posts have post_type = 'post' and pages have post_type = 'page'. Both must be published.
  1499. $where.=" (post_status = 'publish' AND post_type = 'page') ";
  1500. }
  1501. }
  1502. $where.=") ";
  1503. if(is_array($excludes) && count($excludes)>0) {
  1504. $where.=" AND ID NOT IN ('" . implode("','",$excludes) . "')";
  1505. }
  1506. $where.=" AND post_password='' ORDER BY post_modified DESC";
  1507. $sql .= $where;
  1508. if($this->GetOption("b_max_posts")>0) {
  1509. $sql.=" LIMIT 0," . $this->GetOption("b_max_posts");
  1510. }
  1511. $postCount = intval($wpdb->get_var("SELECT COUNT(*) AS cnt FROM `" . $wpdb->posts . "` WHERE ". $where,0,0));
  1512. //Create a new connection because we are using mysql_unbuffered_query and don't want to disturb the WP connection
  1513. //Safe Mode for other plugins which use mysql_query() without a connection handler and will destroy our resultset :(
  1514. $con = $postRes = null;
  1515. //In 2.2, a bug which prevented additional DB connections was fixed
  1516. if(floatval($wp_version) < 2.2) {
  1517. $this->SetOption("b_safemode",true);
  1518. }
  1519. if($this->GetOption("b_safemode")===true) {
  1520. $postRes = mysql_query($sql,$wpdb->dbh);
  1521. if(!$postRes) {
  1522. trigger_error("MySQL query failed: " . mysql_error(),E_USER_NOTICE); //E_USER_NOTICE will be displayed on our debug mode
  1523. return;
  1524. }
  1525. } else {
  1526. $con = mysql_connect(DB_HOST,DB_USER,DB_PASSWORD,true);
  1527. if(!$con) {
  1528. trigger_error("MySQL Connection failed: " . mysql_error(),E_USER_NOTICE);
  1529. return;
  1530. }
  1531. if(!mysql_select_db(DB_NAME,$con)) {
  1532. trigger_error("MySQL DB Select failed: " . mysql_error(),E_USER_NOTICE);
  1533. return;
  1534. }
  1535. $postRes = mysql_unbuffered_query($sql,$con);
  1536. if(!$postRes) {
  1537. trigger_error("MySQL unbuffered query failed: " . mysql_error(),E_USER_NOTICE);
  1538. return;
  1539. }
  1540. }
  1541. if($postRes) {
  1542. //#type $prioProvider GoogleSitemapGeneratorPrioProviderBase
  1543. $prioProvider=NULL;
  1544. if($this->GetOption("b_prio_provider") != '') {
  1545. $providerClass=$this->GetOption('b_prio_provider');
  1546. $prioProvider = new $providerClass($commentCount,$postCount);
  1547. }
  1548. //$posts is used by Alex King's Popularity Contest plugin
  1549. //if($posts == null || !is_array($posts)) {
  1550. // $posts = &$postRes;
  1551. //}
  1552. $z = 1;
  1553. $zz = 1;
  1554. //Default priorities
  1555. $default_prio_posts = $this->GetOption('pr_posts');
  1556. $default_prio_pages = $this->GetOption('pr_pages');
  1557. //Change frequencies
  1558. $cf_pages = $this->GetOption('cf_pages');
  1559. $cf_posts = $this->GetOption('cf_posts');
  1560. $minPrio=$this->GetOption('pr_posts_min');
  1561. //Cycle through all posts and add them
  1562. while($post = mysql_fetch_object($postRes)) {
  1563. //Fill the cache with our DB result. Since it's incomplete (no text-content for example), we will clean it later.
  1564. $cache = array(&$post);
  1565. update_post_cache($cache);
  1566. //Set the current working post for other plugins which depend on "the loop"
  1567. $GLOBALS['post'] = &$post;
  1568. $permalink = get_permalink($post->ID);
  1569. if($permalink != $home && $post->ID != $homePid) {
  1570. $isPage = false;
  1571. if($wpCompat) {
  1572. $isPage = ($post->post_status == 'static');
  1573. } else {
  1574. $isPage = ($post->post_type == 'page');
  1575. }
  1576. //Default Priority if auto calc is disabled
  1577. $prio = 0;
  1578. if($isPage) {
  1579. //Priority for static pages
  1580. $prio = $default_prio_pages;
  1581. } else {
  1582. //Priority for normal posts
  1583. $prio = $default_prio_posts;
  1584. }
  1585. //If priority calc. is enabled, calculate (but only for posts, not pages)!
  1586. if($prioProvider !== null && !$isPage) {
  1587. //Comment count for this post
  1588. $cmtcnt = (isset($comments[$post->ID])?$comments[$post->ID]:0);
  1589. $prio = $prioProvider->GetPostPriority($post->ID, $cmtcnt, $post);
  1590. if($debug) $this->AddElement(new GoogleSitemapGeneratorDebugEntry('Debug: Priority report of postID ' . $post->ID . ': Comments: ' . $cmtcnt . ' of ' . $commentCount . ' = ' . $prio . ' points'));
  1591. }
  1592. if(!$isPage && $minPrio>0 && $prio<$minPrio) {
  1593. $prio = $minPrio;
  1594. }
  1595. //Add it
  1596. $this->AddUrl($permalink,$this->GetTimestampFromMySql(($post->post_modified_gmt && $post->post_modified_gmt!='0000-00-00 00:00:00'?$post->post_modified_gmt:$post->post_date_gmt)),($isPage?$cf_pages:$cf_posts),$prio);
  1597. if($inSubPages) {
  1598. $subPage = '';
  1599. for($p = 1; $p <= $post->postPages; $p++) {
  1600. if(get_option('permalink_structure') == '') {
  1601. $subPage = $permalink . '&amp;page=' . ($p+1);
  1602. } else {
  1603. $subPage = trailingslashit($permalink) . user_trailingslashit($p+1, 'single_paged');
  1604. }
  1605. $this->AddUrl($subPage,$this->GetTimestampFromMySql(($post->post_modified_gmt && $post->post_modified_gmt!='0000-00-00 00:00:00'?$post->post_modified_gmt:$post->post_date_gmt)),($isPage?$cf_pages:$cf_posts),$prio);
  1606. }
  1607. }
  1608. // Multilingual Support with qTranslate, thanks to Qian Qin
  1609. if($useQTransLate) {
  1610. global $q_config;
  1611. foreach(qtrans_getEnabledLanguages($post->post_content) as $language) {
  1612. if($language!=$q_config['default_language']) {
  1613. $this->AddUrl(qtrans_convertURL($permalink,$language),$this->GetTimestampFromMySql(($post->post_modified_gmt && $post->post_modified_gmt!='0000-00-00 00:00:00'?$post->post_modified_gmt:$post->post_date_gmt)),($isPage?$cf_pages:$cf_posts),$prio);
  1614. }
  1615. }
  1616. }
  1617. }
  1618. //Update the status every 100 posts and at the end.
  1619. //If the script breaks because of memory or time limit,
  1620. //we have a "last reponded" value which can be compared to the server settings
  1621. if($zz==100 || $z == $postCount) {
  1622. $status->SaveStep($z);
  1623. $zz=0;
  1624. } else $zz++;
  1625. $z++;
  1626. //Clean cache because it's incomplete
  1627. if(version_compare($wp_version,"2.5",">=")) {
  1628. //WP 2.5 makes a mysql query for every clean_post_cache to clear the child cache
  1629. //so I've copied the function here until a patch arrives...
  1630. wp_cache_delete($post->ID, 'posts');
  1631. wp_cache_delete($post->ID, 'post_meta');
  1632. clean_object_term_cache($post->ID, 'post');
  1633. } else {
  1634. clean_post_cache($post->ID);
  1635. }
  1636. }
  1637. unset($postRes);
  1638. unset($prioProvider);
  1639. if($this->GetOption("b_safemode")!==true && $con) mysql_close($con);
  1640. }
  1641. if($debug) $this->AddElement(new GoogleSitemapGeneratorDebugEntry("Debug: End Postings"));
  1642. }
  1643. //Add the cats
  1644. if($this->GetOption("in_cats")) {
  1645. if($debug) $this->AddElement(new GoogleSitemapGeneratorDebugEntry("Debug: Start Cats"));
  1646. $exclCats = $this->GetOption("b_exclude_cats"); // Excluded cats
  1647. if($exclCats == null) $exclCats=array();
  1648. if(!$this->IsTaxonomySupported()) {
  1649. $catsRes=$wpdb->get_results("
  1650. SELECT
  1651. c.cat_ID AS ID,
  1652. MAX(p.post_modified_gmt) AS last_mod
  1653. FROM
  1654. `" . $wpdb->categories . "` c,
  1655. `" . $wpdb->post2cat . "` pc,
  1656. `" . $wpdb->posts . "` p
  1657. WHERE
  1658. pc.category_id = c.cat_ID
  1659. AND p.ID = pc.post_id
  1660. AND p.post_status = 'publish'
  1661. AND p.post_type='post'
  1662. GROUP
  1663. BY c.cat_id
  1664. ");
  1665. if($catsRes) {
  1666. foreach($catsRes as $cat) {
  1667. if($cat && $cat->ID && $cat->ID>0 && !in_array($cat->ID, $exclCats)) {
  1668. if($debug) if($debug) $this->AddElement(new GoogleSitemapGeneratorDebugEntry("Cat-ID:" . $cat->ID));
  1669. $this->AddUrl(get_category_link($cat->ID),$this->GetTimestampFromMySql($cat->last_mod),$this->GetOption("cf_cats"),$this->GetOption("pr_cats"));
  1670. }
  1671. }
  1672. }
  1673. } else {
  1674. $cats = get_terms("category",array("hide_empty"=>true,"hierarchical"=>false));
  1675. if($cats && is_array($cats) && count($cats)>0) {
  1676. foreach($cats AS $cat) {
  1677. if(!in_array($cat->term_id, $exclCats)) $this->AddUrl(get_category_link($cat->term_id),0,$this->GetOption("cf_cats"),$this->GetOption("pr_cats"));
  1678. }
  1679. }
  1680. }
  1681. if($debug) $this->AddElement(new GoogleSitemapGeneratorDebugEntry("Debug: End Cats"));
  1682. }
  1683. //Add the archives
  1684. if($this->GetOption("in_arch")) {
  1685. if($debug) $this->AddElement(new GoogleSitemapGeneratorDebugEntry("Debug: Start Archive"));
  1686. $now = current_time('mysql');
  1687. //WP2.1 introduced post_status='future', for earlier WP versions we need to check the post_date_gmt
  1688. $arcresults = $wpdb->get_results("
  1689. SELECT DISTINCT
  1690. YEAR(post_date_gmt) AS `year`,
  1691. MONTH(post_date_gmt) AS `month`,
  1692. MAX(post_date_gmt) as last_mod,
  1693. count(ID) as posts
  1694. FROM
  1695. $wpdb->posts
  1696. WHERE
  1697. post_date < '$now'
  1698. AND post_status = 'publish'
  1699. AND post_type = 'post'
  1700. " . (floatval($wp_version) < 2.1?"AND {$wpdb->posts}.post_date_gmt <= '" . gmdate('Y-m-d H:i:59') . "'":"") . "
  1701. GROUP BY
  1702. YEAR(post_date_gmt),
  1703. MONTH(post_date_gmt)
  1704. ORDER BY
  1705. post_date_gmt DESC");
  1706. if ($arcresults) {
  1707. foreach ($arcresults as $arcresult) {
  1708. $url = get_month_link($arcresult->year, $arcresult->month);
  1709. $changeFreq="";
  1710. //Archive is the current one
  1711. if($arcresult->month==date("n") && $arcresult->year==date("Y")) {
  1712. $changeFreq=$this->GetOption("cf_arch_curr");
  1713. } else { // Archive is older
  1714. $changeFreq=$this->GetOption("cf_arch_old");
  1715. }
  1716. $this->AddUrl($url,$this->GetTimestampFromMySql($arcresult->last_mod),$changeFreq,$this->GetOption("pr_arch"));
  1717. }
  1718. }
  1719. if($debug) $this->AddElement(new GoogleSitemapGeneratorDebugEntry("Debug: End Archive"));
  1720. }
  1721. //Add the author pages
  1722. if($this->GetOption("in_auth")) {
  1723. if($debug) $this->AddElement(new GoogleSitemapGeneratorDebugEntry("Debug: Start Author pages"));
  1724. $linkFunc = null;
  1725. //get_author_link is deprecated in WP 2.1, try to use get_author_posts_url first.
  1726. if(function_exists('get_author_posts_url')) {
  1727. $linkFunc = 'get_author_posts_url';
  1728. } else if(function_exists('get_author_link')) {
  1729. $linkFunc = 'get_author_link';
  1730. }
  1731. //Who knows what happens in later WP versions, so check again if it worked
  1732. if($linkFunc !== null) {
  1733. //Unfortunately there is no API function to get all authors, so we have to do it the dirty way...
  1734. //We retrieve only users with published and not password protected posts (and not pages)
  1735. //WP2.1 introduced post_status='future', for earlier WP versions we need to check the post_date_gmt
  1736. $sql = "SELECT DISTINCT
  1737. p.ID,
  1738. u.user_nicename,
  1739. MAX(p.post_modified_gmt) AS last_post
  1740. FROM
  1741. {$wpdb->users} u,
  1742. {$wpdb->posts} p
  1743. WHERE
  1744. p.post_author = u.ID
  1745. AND p.post_status = 'publish'
  1746. AND p.post_type = 'post'
  1747. AND p.post_password = ''
  1748. " . (floatval($wp_version) < 2.1?"AND p.post_date_gmt <= '" . gmdate('Y-m-d H:i:59') . "'":"") . "
  1749. GROUP BY
  1750. u.ID,
  1751. u.user_nicename";
  1752. $authors = $wpdb->get_results($sql);
  1753. if($authors && is_array($authors)) {
  1754. foreach($authors as $author) {
  1755. if($debug) if($debug) $this->AddElement(new GoogleSitemapGeneratorDebugEntry("Author-ID:" . $author->ID));
  1756. $url = ($linkFunc=='get_author_posts_url'?get_author_posts_url($author->ID,$author->user_nicename):get_author_link(false,$author->ID,$author->user_nicename));
  1757. $this->AddUrl($url,$this->GetTimestampFromMySql($author->last_post),$this->GetOption("cf_auth"),$this->GetOption("pr_auth"));
  1758. }
  1759. }
  1760. } else {
  1761. //Too bad, no author pages for you :(
  1762. if($debug) $this->AddElement(new GoogleSitemapGeneratorDebugEntry("Debug: No valid author link function found"));
  1763. }
  1764. if($debug) $this->AddElement(new GoogleSitemapGeneratorDebugEntry("Debug: End Author pages"));
  1765. }
  1766. //Add tag pages
  1767. if($this->GetOption("in_tags") && $this->IsTaxonomySupported()) {
  1768. if($debug) $this->AddElement(new GoogleSitemapGeneratorDebugEntry("Debug: Start Tags"));
  1769. $tags = get_terms("post_tag",array("hide_empty"=>true,"hierarchical"=>false));
  1770. if($tags && is_array($tags) && count($tags)>0) {
  1771. foreach($tags AS $tag) {
  1772. $this->AddUrl(get_tag_link($tag->term_id),0,$this->GetOption("cf_tags"),$this->GetOption("pr_tags"));
  1773. }
  1774. }
  1775. if($debug) $this->AddElement(new GoogleSitemapGeneratorDebugEntry("Debug: End Tags"));
  1776. }
  1777. //Add custom taxonomy pages
  1778. if($this->GetOption("in_tax") && $this->IsTaxonomySupported()) {
  1779. if($debug) $this->AddElement(new GoogleSitemapGeneratorDebugEntry("Debug: Start custom taxonomies"));
  1780. $enabledTaxonomies = $this->GetOption("in_tax");
  1781. $taxList = array();
  1782. foreach ($enabledTaxonomies as $taxName) {
  1783. $taxonomy = get_taxonomy($taxName);
  1784. if($taxonomy) $taxList[] = $wpdb->escape($taxonomy->name);
  1785. }
  1786. if(count($taxList)>0) {
  1787. //We're selecting all term information (t.*) plus some additional fields
  1788. //like the last mod date and the taxonomy name, so WP doesnt need to make
  1789. //additional queries to build the permalink structure.
  1790. //This does NOT work for categories and tags yet, because WP uses get_category_link
  1791. //and get_tag_link internally and that would cause one additional query per term!
  1792. $sql="
  1793. SELECT
  1794. t.*,
  1795. tt.taxonomy AS _taxonomy,
  1796. UNIX_TIMESTAMP(MAX(post_date_gmt)) as _mod_date
  1797. FROM
  1798. {$wpdb->posts} p ,
  1799. {$wpdb->term_relationships} r,
  1800. {$wpdb->terms} t,
  1801. {$wpdb->term_taxonomy} tt
  1802. WHERE
  1803. p.ID = r.object_id
  1804. AND p.post_status = 'publish'
  1805. AND p.post_type = 'post'
  1806. AND p.post_password = ''
  1807. AND r.term_taxonomy_id = t.term_id
  1808. AND t.term_id = tt.term_id
  1809. AND tt.count > 0
  1810. AND tt.taxonomy IN ('" . implode("','",$taxList) . "')
  1811. GROUP BY
  1812. t.term_id";
  1813. $termInfo = $wpdb->get_results($sql);
  1814. foreach($termInfo AS $term) {
  1815. $this->AddUrl(get_term_link($term,$term->_taxonomy),$term->_mod_date ,$this->GetOption("cf_tags"),$this->GetOption("pr_tags"));
  1816. }
  1817. }
  1818. if($debug) $this->AddElement(new GoogleSitemapGeneratorDebugEntry("Debug: End custom taxonomies"));
  1819. }
  1820. //Add the custom pages
  1821. if($debug) $this->AddElement(new GoogleSitemapGeneratorDebugEntry("Debug: Start Custom Pages"));
  1822. if($this->_pages && is_array($this->_pages) && count($this->_pages)>0) {
  1823. //#type $page GoogleSitemapGeneratorPage
  1824. foreach($this->_pages AS $page) {
  1825. $this->AddUrl($page->GetUrl(),$page->getLastMod(),$page->getChangeFreq(),$page->getPriority());
  1826. }
  1827. }
  1828. if($debug) $this->AddElement(new GoogleSitemapGeneratorDebugEntry("Debug: End Custom Pages"));
  1829. if($debug) $this->AddElement(new GoogleSitemapGeneratorDebugEntry("Debug: Start additional URLs"));
  1830. do_action('sm_buildmap');
  1831. if($debug) $this->AddElement(new GoogleSitemapGeneratorDebugEntry("Debug: End additional URLs"));
  1832. $this->AddElement(new GoogleSitemapGeneratorXmlEntry("</urlset>"));
  1833. $pingUrl='';
  1834. if($this->GetOption("b_xml")) {
  1835. if($this->_fileHandle && fclose($this->_fileHandle)) {
  1836. $this->_fileHandle = null;
  1837. $status->EndXml(true);
  1838. $pingUrl=$this->GetXmlUrl();
  1839. } else $status->EndXml(false,"Could not close the sitemap file.");
  1840. }
  1841. if($this->IsGzipEnabled()) {
  1842. if($this->_fileZipHandle && fclose($this->_fileZipHandle)) {
  1843. $this->_fileZipHandle = null;
  1844. $status->EndZip(true);
  1845. $pingUrl=$this->GetZipUrl();
  1846. } else $status->EndZip(false,"Could not close the zipped sitemap file");
  1847. }
  1848. //Ping Google
  1849. if($this->GetOption("b_ping") && !empty($pingUrl)) {
  1850. $sPingUrl="http://www.google.com/webmasters/sitemaps/ping?sitemap=" . urlencode($pingUrl);
  1851. $status->StartGooglePing($sPingUrl);
  1852. $pingres=$this->RemoteOpen($sPingUrl);
  1853. if($pingres==NULL || $pingres===false) {
  1854. $status->EndGooglePing(false,$this->_lastError);
  1855. trigger_error("Failed to ping Google: " . htmlspecialchars(strip_tags($pingres)),E_USER_NOTICE);
  1856. } else {
  1857. $status->EndGooglePing(true);
  1858. }
  1859. }
  1860. //Ping Ask.com
  1861. if($this->GetOption("b_pingask") && !empty($pingUrl)) {
  1862. $sPingUrl="http://submissions.ask.com/ping?sitemap=" . urlencode($pingUrl);
  1863. $status->StartAskPing($sPingUrl);
  1864. $pingres=$this->RemoteOpen($sPingUrl);
  1865. if($pingres==NULL || $pingres===false || strpos($pingres,"successfully received and added")===false) { //Ask.com returns 200 OK even if there was an error, so we need to check the content.
  1866. $status->EndAskPing(false,$this->_lastError);
  1867. trigger_error("Failed to ping Ask.com: " . htmlspecialchars(strip_tags($pingres)),E_USER_NOTICE);
  1868. } else {
  1869. $status->EndAskPing(true);
  1870. }
  1871. }
  1872. //Ping YAHOO
  1873. if($this->GetOption("b_pingyahoo")===true && $this->GetOption("b_yahookey")!="" && !empty($pingUrl)) {
  1874. $sPingUrl="http://search.yahooapis.com/SiteExplorerService/V1/updateNotification?appid=" . $this->GetOption("b_yahookey") . "&url=" . urlencode($pingUrl);
  1875. $status->StartYahooPing($sPingUrl);
  1876. $pingres=$this->RemoteOpen($sPingUrl);
  1877. if($pingres==NULL || $pingres===false || strpos(strtolower($pingres),"success")===false) {
  1878. trigger_error("Failed to ping YAHOO: " . htmlspecialchars(strip_tags($pingres)),E_USER_NOTICE);
  1879. $status->EndYahooPing(false,$this->_lastError);
  1880. } else {
  1881. $status->EndYahooPing(true);
  1882. }
  1883. }
  1884. //Ping Bing
  1885. if($this->GetOption("b_pingmsn") && !empty($pingUrl)) {
  1886. $sPingUrl="http://www.bing.com/webmaster/ping.aspx?siteMap=" . urlencode($pingUrl);
  1887. $status->StartMsnPing($sPingUrl);
  1888. $pingres=$this->RemoteOpen($sPingUrl);
  1889. if($pingres==NULL || $pingres===false || strpos($pingres,"Thanks for submitting your sitemap")===false) {
  1890. trigger_error("Failed to ping Bing: " . htmlspecialchars(strip_tags($pingres)),E_USER_NOTICE);
  1891. $status->EndMsnPing(false,$this->_lastError);
  1892. } else {
  1893. $status->EndMsnPing(true);
  1894. }
  1895. }
  1896. $status->End();
  1897. $this->_isActive = false;
  1898. //done...
  1899. return $status;
  1900. }
  1901. /**
  1902. * Tries to ping a specific service showing as much as debug output as possible
  1903. * @since 3.1.9
  1904. * @return null
  1905. */
  1906. function ShowPingResult() {
  1907. check_admin_referer('sitemap');
  1908. if(!current_user_can("administrator")) {
  1909. echo '<p>Please log in as admin</p>';
  1910. return;
  1911. }
  1912. $service = !empty($_GET["sm_ping_service"])?$_GET["sm_ping_service"]:null;
  1913. $status = &GoogleSitemapGeneratorStatus::Load();
  1914. if(!$status) die("No build status yet. Build the sitemap first.");
  1915. $url = null;
  1916. switch($service) {
  1917. case "google":
  1918. $url = $status->_googleUrl;
  1919. break;
  1920. case "yahoo":
  1921. $url = $status->_yahooUrl;
  1922. break;
  1923. case "msn":
  1924. $url = $status->_msnUrl;
  1925. break;
  1926. case "ask":
  1927. $url = $status->_askUrl;
  1928. break;
  1929. }
  1930. if(empty($url)) die("Invalid ping url");
  1931. echo '<html><head><title>Ping Test</title>';
  1932. if(function_exists('wp_admin_css')) wp_admin_css('css/global',true);
  1933. echo '</head><body><h1>Ping Test</h1>';
  1934. echo '<p>Trying to ping: <a href="' . $url . '">' . $url . '</a>. The sections below should give you an idea whats going on.</p>';
  1935. //Try to get as much as debug / error output as possible
  1936. $errLevel = error_reporting(E_ALL);
  1937. $errDisplay = ini_set("display_errors",1);
  1938. if(!defined('WP_DEBUG')) define('WP_DEBUG',true);
  1939. echo '<h2>Errors, Warnings, Notices:</h2>';
  1940. if(WP_DEBUG == false) echo "<i>WP_DEBUG was set to false somewhere before. You might not see all debug information until you remove this declaration!</i><br />";
  1941. if(ini_get("display_errors")!=1) echo "<i>Your display_errors setting currently prevents the plugin from showing errors here. Please check your webserver logfile instead.</i><br />";
  1942. $res = $this->RemoteOpen($url);
  1943. echo '<h2>Result (text only):</h2>';
  1944. echo wp_kses($res,array('a' => array('href' => array()),'p' => array(), 'ul' => array(), 'ol' => array(), 'li' => array()));
  1945. echo '<h2>Result (HTML):</h2>';
  1946. echo htmlspecialchars($res);
  1947. //Revert back old values
  1948. error_reporting($errLevel);
  1949. ini_set("display_errors",$errDisplay);
  1950. echo '</body></html>';
  1951. exit;
  1952. }
  1953. /**
  1954. * Opens a remote file using the WordPress API or Snoopy
  1955. * @since 3.0
  1956. * @param $url The URL to open
  1957. * @param $method get or post
  1958. * @param $postData An array with key=>value paris
  1959. * @param $timeout Timeout for the request, by default 10
  1960. * @return mixed False on error, the body of the response on success
  1961. */
  1962. function RemoteOpen($url,$method = 'get', $postData = null, $timeout = 10) {
  1963. global $wp_version;
  1964. //Before WP 2.7, wp_remote_fopen was quite crappy so Snoopy was favoured.
  1965. if(floatval($wp_version) < 2.7) {
  1966. if(!file_exists(ABSPATH . 'wp-includes/class-snoopy.php')) {
  1967. trigger_error('Snoopy Web Request failed: Snoopy not found.',E_USER_NOTICE);
  1968. return false; //Hoah?
  1969. }
  1970. require_once( ABSPATH . 'wp-includes/class-snoopy.php');
  1971. $s = new Snoopy();
  1972. $s->read_timeout = $timeout;
  1973. if($method == 'get') {
  1974. $s->fetch($url);
  1975. } else {
  1976. $s->submit($url,$postData);
  1977. }
  1978. if($s->status != "200") {
  1979. trigger_error('Snoopy Web Request failed: Status: ' . $s->status . "; Content: " . htmlspecialchars($s->results),E_USER_NOTICE);
  1980. }
  1981. return $s->results;
  1982. } else {
  1983. $options = array();
  1984. $options['timeout'] = $timeout;
  1985. if($method == 'get') {
  1986. $response = wp_remote_get( $url, $options );
  1987. } else {
  1988. $response = wp_remote_post($url, array_merge($options,array('body'=>$postData)));
  1989. }
  1990. if ( is_wp_error( $response ) ) {
  1991. $errs = $response->get_error_messages();
  1992. $errs = htmlspecialchars(implode('; ', $errs));
  1993. trigger_error('WP HTTP API Web Request failed: ' . $errs,E_USER_NOTICE);
  1994. return false;
  1995. }
  1996. return $response['body'];
  1997. }
  1998. return false;
  1999. }
  2000. /**
  2001. * Echos option fields for an select field containing the valid change frequencies
  2002. *
  2003. * @since 3.0
  2004. * @access private
  2005. * @author Arne Brachhold
  2006. * @param $currentVal The value which should be selected
  2007. * @return all valid change frequencies as html option fields
  2008. */
  2009. function HtmlGetFreqNames($currentVal) {
  2010. foreach($this->_freqNames AS $k=>$v) {
  2011. echo "<option value=\"$k\" " . $this->HtmlGetSelected($k,$currentVal) .">" . $v . "</option>";
  2012. }
  2013. }
  2014. /**
  2015. * Echos option fields for an select field containing the valid priorities (0- 1.0)
  2016. *
  2017. * @since 3.0
  2018. * @access private
  2019. * @author Arne Brachhold
  2020. * @param $currentVal string The value which should be selected
  2021. * @return 0.0 - 1.0 as html option fields
  2022. */
  2023. function HtmlGetPriorityValues($currentVal) {
  2024. $currentVal=(float) $currentVal;
  2025. for($i=0.0; $i<=1.0; $i+=0.1) {
  2026. $v = number_format($i,1,".","");
  2027. //number_format_i18n is there since WP 2.3
  2028. $t = function_exists('number_format_i18n')?number_format_i18n($i,1):number_format($i,1);
  2029. echo "<option value=\"" . $v . "\" " . $this->HtmlGetSelected("$i","$currentVal") .">";
  2030. echo $t;
  2031. echo "</option>";
  2032. }
  2033. }
  2034. /**
  2035. * Returns the checked attribute if the given values match
  2036. *
  2037. * @since 3.0
  2038. * @access private
  2039. * @author Arne Brachhold
  2040. * @param $val string The current value
  2041. * @param $equals string The value to match
  2042. * @return The checked attribute if the given values match, an empty string if not
  2043. */
  2044. function HtmlGetChecked($val,$equals) {
  2045. if($val==$equals) return $this->HtmlGetAttribute("checked");
  2046. else return "";
  2047. }
  2048. /**
  2049. * Returns the selected attribute if the given values match
  2050. *
  2051. * @since 3.0
  2052. * @access private
  2053. * @author Arne Brachhold
  2054. * @param $val string The current value
  2055. * @param $equals string The value to match
  2056. * @return The selected attribute if the given values match, an empty string if not
  2057. */
  2058. function HtmlGetSelected($val,$equals) {
  2059. if($val==$equals) return $this->HtmlGetAttribute("selected");
  2060. else return "";
  2061. }
  2062. /**
  2063. * Returns an formatted attribute. If the value is NULL, the name will be used.
  2064. *
  2065. * @since 3.0
  2066. * @access private
  2067. * @author Arne Brachhold
  2068. * @param $attr string The attribute name
  2069. * @param $value string The attribute value
  2070. * @return The formatted attribute
  2071. */
  2072. function HtmlGetAttribute($attr,$value=NULL) {
  2073. if($value==NULL) $value=$attr;
  2074. return " " . $attr . "=\"" . $value . "\" ";
  2075. }
  2076. /**
  2077. * Returns an array with GoogleSitemapGeneratorPage objects which is generated from POST values
  2078. *
  2079. * @since 3.0
  2080. * @see GoogleSitemapGeneratorPage
  2081. * @access private
  2082. * @author Arne Brachhold
  2083. * @return array An array with GoogleSitemapGeneratorPage objects
  2084. */
  2085. function HtmlApplyPages() {
  2086. // Array with all page URLs
  2087. $pages_ur=(!isset($_POST["sm_pages_ur"]) || !is_array($_POST["sm_pages_ur"])?array():$_POST["sm_pages_ur"]);
  2088. //Array with all priorities
  2089. $pages_pr=(!isset($_POST["sm_pages_pr"]) || !is_array($_POST["sm_pages_pr"])?array():$_POST["sm_pages_pr"]);
  2090. //Array with all change frequencies
  2091. $pages_cf=(!isset($_POST["sm_pages_cf"]) || !is_array($_POST["sm_pages_cf"])?array():$_POST["sm_pages_cf"]);
  2092. //Array with all lastmods
  2093. $pages_lm=(!isset($_POST["sm_pages_lm"]) || !is_array($_POST["sm_pages_lm"])?array():$_POST["sm_pages_lm"]);
  2094. //Array where the new pages are stored
  2095. $pages=array();
  2096. //Loop through all defined pages and set their properties into an object
  2097. if(isset($_POST["sm_pages_mark"]) && is_array($_POST["sm_pages_mark"])) {
  2098. for($i=0; $i<count($_POST["sm_pages_mark"]); $i++) {
  2099. //Create new object
  2100. $p=new GoogleSitemapGeneratorPage();
  2101. if(substr($pages_ur[$i],0,4)=="www.") $pages_ur[$i]="http://" . $pages_ur[$i];
  2102. $p->SetUrl($pages_ur[$i]);
  2103. $p->SetProprity($pages_pr[$i]);
  2104. $p->SetChangeFreq($pages_cf[$i]);
  2105. //Try to parse last modified, if -1 (note ===) automatic will be used (0)
  2106. $lm=(!empty($pages_lm[$i])?strtotime($pages_lm[$i],time()):-1);
  2107. if($lm===-1) $p->setLastMod(0);
  2108. else $p->setLastMod($lm);
  2109. //Add it to the array
  2110. array_push($pages,$p);
  2111. }
  2112. }
  2113. return $pages;
  2114. }
  2115. /**
  2116. * Converts a mysql datetime value into a unix timestamp
  2117. *
  2118. * @param The value in the mysql datetime format
  2119. * @return int The time in seconds
  2120. */
  2121. function GetTimestampFromMySql($mysqlDateTime) {
  2122. list($date, $hours) = split(' ', $mysqlDateTime);
  2123. list($year,$month,$day) = split('-',$date);
  2124. list($hour,$min,$sec) = split(':',$hours);
  2125. return mktime(intval($hour), intval($min), intval($sec), intval($month), intval($day), intval($year));
  2126. }
  2127. /**
  2128. * Returns a link pointing to a spcific page of the authors website
  2129. *
  2130. * @since 3.0
  2131. * @param The page to link to
  2132. * @return string The full url
  2133. */
  2134. function GetRedirectLink($redir) {
  2135. return trailingslashit("http://www.arnebrachhold.de/redir/" . $redir);
  2136. }
  2137. /**
  2138. * Returns a link pointing back to the plugin page in WordPress
  2139. *
  2140. * @since 3.0
  2141. * @return string The full url
  2142. */
  2143. function GetBackLink() {
  2144. global $wp_version;
  2145. $url = '';
  2146. //admin_url was added in WP 2.6.0
  2147. if(function_exists("admin_url")) $url = admin_url("options-general.php?page=" . GoogleSitemapGeneratorLoader::GetBaseName());
  2148. else $url = $_SERVER['PHP_SELF'] . "?page=" . GoogleSitemapGeneratorLoader::GetBaseName();
  2149. //Some browser cache the page... great! So lets add some no caching params depending on the WP and plugin version
  2150. $url.='&sm_wpv=' . $wp_version . '&sm_pv=' . GoogleSitemapGeneratorLoader::GetVersion();
  2151. return $url;
  2152. }
  2153. /**
  2154. * Shows the option page of the plugin. Before 3.1.1, this function was basically the UI, afterwards the UI was outsourced to another class
  2155. *
  2156. * @see GoogleSitemapGeneratorUI
  2157. * @since 3.0
  2158. * @return bool
  2159. */
  2160. function HtmlShowOptionsPage() {
  2161. $ui = $this->GetUI();
  2162. if($ui) {
  2163. $ui->HtmlShowOptionsPage();
  2164. return true;
  2165. }
  2166. return false;
  2167. }
  2168. /**
  2169. * Includes the user interface class and intializes it
  2170. *
  2171. * @since 3.1.1
  2172. * @see GoogleSitemapGeneratorUI
  2173. * @return GoogleSitemapGeneratorUI
  2174. */
  2175. function GetUI() {
  2176. global $wp_version;
  2177. if($this->_ui === null) {
  2178. $className='GoogleSitemapGeneratorUI';
  2179. $fileName='sitemap-ui.php';
  2180. if(!class_exists($className)) {
  2181. $path = trailingslashit(dirname(__FILE__));
  2182. if(!file_exists( $path . $fileName)) return false;
  2183. require_once($path. $fileName);
  2184. }
  2185. $this->_ui = new $className($this);
  2186. }
  2187. return $this->_ui;
  2188. }
  2189. function HtmlShowHelp() {
  2190. }
  2191. }