1: <?php
2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14:
15:
16: defined('CON_FRAMEWORK') || die('Illegal call: Missing framework initialization - request aborted.');
17:
18: 19: 20: 21: 22: 23:
24: class cUpdateNotifier {
25:
26: 27: 28: 29: 30:
31: protected $sMinorRelease = "";
32:
33: 34: 35: 36: 37:
38: protected $sVendorHost = "www.contenido.org";
39:
40: 41: 42: 43: 44:
45: protected $sVendorHostPath = "con_version_check_feeds/";
46:
47: 48: 49: 50: 51:
52: protected $sVendorXMLFile = "vendor.xml";
53:
54: 55: 56: 57: 58:
59: protected $sVendorRssDeFile = "rss_de.xml";
60:
61: 62: 63: 64: 65:
66: protected $sVendorRssEnFile = "rss_en.xml";
67:
68: 69: 70: 71: 72:
73: protected $sRSSFile = "";
74:
75: 76: 77: 78: 79:
80: protected $sTimestampCacheFile = "update.txt";
81:
82: 83: 84: 85: 86:
87: protected $sXMLContent = "";
88:
89: 90: 91: 92: 93:
94: protected $sRSSContent = "";
95:
96: 97: 98: 99: 100:
101: protected $sVendorVersion = "";
102:
103: 104: 105: 106: 107:
108: protected $sVendorURL = "http://www.contenido.org/de/redir";
109:
110: 111: 112: 113: 114:
115: protected $sBackendLanguage = "";
116:
117: 118: 119: 120: 121:
122: protected $sCacheDirectory = "";
123:
124: 125: 126: 127: 128:
129: protected $oXML = NULL;
130:
131: 132: 133: 134: 135:
136: protected $oProperties = NULL;
137:
138: 139: 140: 141: 142:
143: protected $oSession = NULL;
144:
145: 146: 147: 148: 149:
150: protected $iConnectTimeout = 3;
151:
152: 153: 154: 155: 156:
157: protected $iCacheDuration = 60;
158:
159: 160: 161: 162: 163:
164: protected $bEnableCheck = false;
165:
166: 167: 168: 169: 170:
171: protected $bEnableCheckRss = false;
172:
173: 174: 175: 176: 177: 178:
179: protected $bNoWritePermissions = false;
180:
181: 182: 183: 184: 185:
186: protected $bEnableView = false;
187:
188: 189: 190: 191: 192:
193: protected $bUpdateNecessity = false;
194:
195: 196: 197: 198: 199:
200: private $bVendorHostReachable = true;
201:
202: 203: 204: 205: 206:
207: protected $aPropConf = array(
208: "itemType" => "update",
209: "itemID" => 1,
210: "type" => "file_check",
211: "name" => "xml"
212: );
213:
214: 215: 216: 217: 218:
219: protected $aSysPropConf = array(
220: "type" => "update",
221: "name" => "check"
222: );
223:
224: 225: 226: 227: 228:
229: protected $aSysPropConfRss = array(
230: "type" => "update",
231: "name" => "news_feed"
232: );
233:
234: 235: 236: 237: 238:
239: protected $aSysPropConfPeriod = array(
240: "type" => "update",
241: "name" => "check_period"
242: );
243:
244: 245: 246: 247: 248:
249: protected $aCfg = array();
250:
251: 252: 253: 254: 255: 256: 257: 258: 259:
260: public function __construct($aCfg, $oUser, $oPerm, $oSession, $sBackendLanguage) {
261: $this->oProperties = new cApiPropertyCollection();
262: $this->oSession = $oSession;
263: $this->aCfg = $aCfg;
264: $this->sBackendLanguage = $sBackendLanguage;
265:
266: if ($oPerm->isSysadmin($oUser) != 1) {
267: $this->bEnableView = false;
268: } else {
269: $this->bEnableView = true;
270:
271: $sAction = $_GET['do'];
272: if ($sAction != "") {
273: $this->updateSystemProperty($sAction);
274: }
275:
276: $sPropUpdate = getSystemProperty($this->aSysPropConf['type'], $this->aSysPropConf['name']);
277: $sPropRSS = getSystemProperty($this->aSysPropConfRss['type'], $this->aSysPropConfRss['name']);
278: $sPeriod = getSystemProperty($this->aSysPropConfPeriod['type'], $this->aSysPropConfPeriod['name']);
279: $iPeriod = cSecurity::toInteger($sPeriod);
280:
281: if ($sPropUpdate == "true" || $sPropRSS == "true") {
282:
283: if ($sPropUpdate == "true") {
284: $this->bEnableCheck = true;
285: }
286:
287: if ($sPropRSS == "true") {
288: $this->bEnableCheckRss = true;
289: }
290:
291:
292: if ($iPeriod >= 60) {
293: $this->iCacheDuration = $iPeriod;
294: } else {
295: $this->iCacheDuration = 60;
296: }
297:
298: $this->setCachePath();
299: if ($this->sCacheDirectory != "") {
300: $this->setRSSFile();
301: $this->detectMinorRelease();
302: $this->checkUpdateNecessity();
303: $this->readVendorContent();
304: }
305: }
306: }
307: }
308:
309: 310: 311:
312: protected function setRSSFile() {
313: if ($this->sBackendLanguage == "de_DE") {
314: $this->sRSSFile = $this->sVendorRssDeFile;
315: } else {
316: $this->sRSSFile = $this->sVendorRssEnFile;
317: }
318: }
319:
320: 321: 322: 323: 324:
325: protected function updateSystemProperty($sAction) {
326: if ($sAction == "activate") {
327: setSystemProperty($this->aSysPropConf['type'], $this->aSysPropConf['name'], "true");
328: } else if ($sAction == "deactivate") {
329: setSystemProperty($this->aSysPropConf['type'], $this->aSysPropConf['name'], "false");
330: } else if ($sAction == "activate_rss") {
331: setSystemProperty($this->aSysPropConfRss['type'], $this->aSysPropConfRss['name'], "true");
332: } else if ($sAction == "deactivate_rss") {
333: setSystemProperty($this->aSysPropConfRss['type'], $this->aSysPropConfRss['name'], "false");
334: }
335: }
336:
337: 338: 339:
340: protected function setCachePath() {
341: $sCachePath = $this->aCfg['path']['contenido_cache'];
342: if (!is_dir($sCachePath)) {
343: mkdir($sCachePath, 0777);
344: }
345:
346: if (!is_writable($sCachePath)) {
347:
348: $this->bNoWritePermissions = true;
349: } else {
350: $this->sCacheDirectory = $sCachePath;
351: }
352: }
353:
354: 355: 356: 357:
358: protected function checkUpdateNecessity() {
359: $bUpdateNecessity = false;
360:
361: $aCheckFiles = array(
362: $this->sVendorXMLFile,
363: $this->sVendorRssDeFile,
364: $this->sVendorRssEnFile,
365: $this->sTimestampCacheFile
366: );
367: foreach ($aCheckFiles as $sFilename) {
368: if (!cFileHandler::exists($this->sCacheDirectory . $sFilename)) {
369: $bUpdateNecessity = true;
370: break;
371: }
372: }
373:
374: if ($bUpdateNecessity == false) {
375: $iLastUpdate = (int) cFileHandler::read($this->sCacheDirectory . $this->sTimestampCacheFile);
376:
377: $iCheckTimestamp = $iLastUpdate + ($this->iCacheDuration * 60);
378: $iCurrentTime = time();
379:
380: if ($iCheckTimestamp > $iCurrentTime) {
381: $bUpdateNecessity = false;
382: } else {
383: $bUpdateNecessity = true;
384: }
385: }
386:
387: $this->bUpdateNecessity = $bUpdateNecessity;
388: }
389:
390: 391: 392:
393: protected function detectMinorRelease() {
394: $sVersion = CON_VERSION;
395: $aExplode = explode(".", $sVersion);
396: $sMinorRelease = "con" . $aExplode[0] . $aExplode[1];
397: $this->sMinorRelease = $sMinorRelease;
398: }
399:
400: 401: 402: 403:
404: protected function readVendorContent() {
405: $this->sXMLContent = "";
406:
407: if ($this->bUpdateNecessity == true) {
408: $aXmlContent = $this->getVendorHostFiles();
409:
410: if (isset($aXmlContent[$this->sVendorXMLFile]) && isset($aXmlContent[$this->sVendorRssDeFile]) && isset($aXmlContent[$this->sVendorRssEnFile])) {
411: $this->handleVendorUpdate($aXmlContent);
412: }
413: } else {
414:
415: $sXMLContent = cFileHandler::read($this->sCacheDirectory . $this->sVendorXMLFile);
416: $aRSSContent[$this->sVendorRssDeFile] = cFileHandler::read($this->sCacheDirectory . $this->sVendorRssDeFile);
417: $aRSSContent[$this->sVendorRssEnFile] = cFileHandler::read($this->sCacheDirectory . $this->sVendorRssEnFile);
418:
419: $sXMLHash = md5($sXMLContent . $aRSSContent[$this->sVendorRssDeFile] . $aRSSContent[$this->sVendorRssEnFile]);
420: $sPropertyHash = $this->getHashProperty();
421: if ($sXMLHash == $sPropertyHash) {
422: $this->sXMLContent = $sXMLContent;
423: $this->sRSSContent = $aRSSContent[$this->sRSSFile];
424: } else {
425: $aXmlContent = $this->getVendorHostFiles();
426: if (isset($aXmlContent[$this->sVendorXMLFile]) && isset($aXmlContent[$this->sVendorRssDeFile]) && isset($aXmlContent[$this->sVendorRssEnFile])) {
427: $this->handleVendorUpdate($aXmlContent);
428: }
429: }
430: }
431:
432:
433:
434: if ($this->sXMLContent != "") {
435:
436: $this->oXML = simplexml_load_string($this->sXMLContent);
437: if (!is_object($this->oXML)) {
438: $sErrorMessage = i18n('Unable to check for new updates!') . " " . i18n('Could not handle server response!');
439: $this->sErrorOutput = $this->renderOutput($sErrorMessage);
440: } else {
441: $oVersion = $this->oXML->xpath("/fourforbusiness/contenido/releases/" . $this->sMinorRelease);
442: if (!isset($oVersion[0])) {
443: $sErrorMessage = i18n('Unable to check for new updates!') . " " . i18n('Could not determine vendor version!');
444: $this->sErrorOutput = $this->renderOutput($sErrorMessage);
445: } else {
446: $this->sVendorVersion = $oVersion[0];
447: }
448: }
449: }
450: }
451:
452: 453: 454: 455: 456:
457: protected function handleVendorUpdate($aXMLContent) {
458: $bValidXMLFile = true;
459: $bValidDeRSSFile = true;
460: $bValidEnRSSFile = true;
461:
462: $sCheckXML = stristr($aXMLContent[$this->sVendorXMLFile], "<fourforbusiness>");
463: if ($sCheckXML == false) {
464: $bValidXMLFile = false;
465: }
466:
467: $sCheckDeRSS = stristr($aXMLContent[$this->sVendorRssDeFile], "<channel>");
468: if ($sCheckDeRSS == false) {
469: $bValidDeRSSFile = false;
470: }
471:
472: $sCheckEnRSS = stristr($aXMLContent[$this->sVendorRssEnFile], "<channel>");
473: if ($sCheckEnRSS == false) {
474: $bValidEnRSSFile = false;
475: }
476:
477:
478:
479:
480:
481: if ($bValidXMLFile != true) {
482: if (cFileHandler::exists($this->sCacheDirectory . $this->sVendorXMLFile)) {
483: $sXMLReplace = cFileHandler::read($this->sCacheDirectory . $this->sVendorXMLFile);
484: } else {
485: $sXMLReplace = "<error>The vendor host file at " . $this->sVendorHost . " is not availiable!</error>";
486: }
487: $aXMLContent[$this->sVendorXMLFile] = $sXMLReplace;
488: }
489:
490: if ($bValidDeRSSFile != true) {
491: if (cFileHandler::exists($this->sCacheDirectory . $this->sVendorRssDeFile)) {
492: $sDeRSSReplace = cFileHandler::read($this->sCacheDirectory . $this->sVendorRssDeFile);
493: } else {
494: $sDeRSSReplace = "<rss></rss>";
495: }
496: $aXMLContent[$this->sVendorRssDeFile] = $sDeRSSReplace;
497: }
498:
499: if ($bValidEnRSSFile != true) {
500: if (cFileHandler::exists($this->sCacheDirectory . $this->sVendorRssEnFile)) {
501: $sEnRSSReplace = cFileHandler::read($this->sCacheDirectory . $this->sVendorRssEnFile);
502: } else {
503: $sEnRSSReplace = "<rss></rss>";
504: }
505: $aXMLContent[$this->sVendorRssEnFile] = $sEnRSSReplace;
506: }
507:
508: $this->sXMLContent = $aXMLContent[$this->sVendorXMLFile];
509: $this->sRSSContent = $aXMLContent[$this->sRSSFile];
510: $this->updateCacheFiles($aXMLContent);
511: $this->updateHashProperty($aXMLContent);
512: }
513:
514: 515: 516: 517: 518:
519: protected function getVendorHostFiles() {
520: $aXMLContent = array();
521:
522: $sXMLUpdate = $this->fetchUrl($this->sVendorHostPath . $this->sVendorXMLFile);
523:
524:
525: $sDeRSSContent = $this->fetchUrl($this->sVendorHostPath . $this->sVendorRssDeFile);
526:
527:
528: $sEnRSSContent = $this->fetchUrl($this->sVendorHostPath . $this->sVendorRssEnFile);
529:
530: $aXMLContent[$this->sVendorXMLFile] = $sXMLUpdate;
531: $aXMLContent[$this->sVendorRssDeFile] = $sDeRSSContent;
532: $aXMLContent[$this->sVendorRssEnFile] = $sEnRSSContent;
533:
534: return $aXMLContent;
535: }
536:
537: 538: 539: 540: 541:
542: protected function updateCacheFiles($aRSSContent) {
543: $aWriteCache = array();
544: $aWriteCache[$this->sVendorXMLFile] = $this->sXMLContent;
545: $aWriteCache[$this->sVendorRssDeFile] = $aRSSContent[$this->sVendorRssDeFile];
546: $aWriteCache[$this->sVendorRssEnFile] = $aRSSContent[$this->sVendorRssEnFile];
547: $aWriteCache[$this->sTimestampCacheFile] = time();
548:
549: if (is_writable($this->sCacheDirectory)) {
550: foreach ($aWriteCache as $sFile => $sContent) {
551: $sCacheFile = $this->sCacheDirectory . $sFile;
552: cFileHandler::write($sCacheFile, $sContent, false);
553: }
554: }
555: }
556:
557: 558: 559: 560: 561:
562: protected function getHashProperty() {
563: $sProperty = $this->oProperties->getValue($this->aPropConf['itemType'], $this->aPropConf['itemID'], $this->aPropConf['type'], $this->aPropConf['name']);
564: return $sProperty;
565: }
566:
567: 568: 569: 570: 571:
572: protected function updateHashProperty($aXMLContent) {
573: $sXML = $aXMLContent[$this->sVendorXMLFile];
574: $sDeRSS = $aXMLContent[$this->sVendorRssDeFile];
575: $sEnRSS = $aXMLContent[$this->sVendorRssEnFile];
576:
577: $sPropValue = md5($sXML . $sDeRSS . $sEnRSS);
578: $this->oProperties->setValue($this->aPropConf['itemType'], $this->aPropConf['itemID'], $this->aPropConf['type'], $this->aPropConf['name'], $sPropValue);
579: }
580:
581: 582: 583: 584: 585:
586: protected function checkPatchLevel() {
587: $sVersionCompare = version_compare(CON_VERSION, $this->sVendorVersion);
588: return $sVersionCompare;
589: }
590:
591: 592: 593: 594: 595:
596: protected function getDownloadURL() {
597: $sVendorURLVersion = str_replace(".", "_", $this->sVendorVersion);
598: $sVendorURL = $this->sVendorURL . "/Contenido_" . $sVendorURLVersion;
599: return $sVendorURL;
600: }
601:
602: 603: 604: 605: 606: 607:
608: protected function renderOutput($sMessage) {
609: $oTpl = new cTemplate();
610: $oTpl->set('s', 'UPDATE_MESSAGE', $sMessage);
611:
612: if ($this->bEnableCheck == true) {
613: $oTpl->set('s', 'UPDATE_ACTIVATION', i18n('Disable update notification'));
614: $oTpl->set('s', 'IMG_BUT_UPDATE', 'but_cancel.gif');
615: $oTpl->set('s', 'LABEL_BUT_UPDATE', i18n('Disable notification'));
616: $oTpl->set('s', 'URL_UPDATE', $this->oSession->url('main.php?frame=4&area=mycontenido&do=deactivate'));
617: } else {
618: $oTpl->set('s', 'UPDATE_ACTIVATION', i18n('Enable update notification (recommended)'));
619: $oTpl->set('s', 'IMG_BUT_UPDATE', 'but_ok.gif');
620: $oTpl->set('s', 'LABEL_BUT_UPDATE', i18n('Enable notification'));
621: $oTpl->set('s', 'URL_UPDATE', $this->oSession->url('main.php?frame=4&area=mycontenido&do=activate'));
622: }
623:
624: if ($this->bEnableCheckRss == true) {
625: $oTpl->set('s', 'RSS_ACTIVATION', i18n('Disable RSS notification'));
626: $oTpl->set('s', 'IMG_BUT_RSS', 'but_cancel.gif');
627: $oTpl->set('s', 'LABEL_BUT_RSS', i18n('Disable notification'));
628: $oTpl->set('s', 'URL_RSS', $this->oSession->url('main.php?frame=4&area=mycontenido&do=deactivate_rss'));
629:
630: $oTpl = $this->renderRss($oTpl);
631: } else {
632: $oTpl->set('s', 'RSS_ACTIVATION', i18n('Enable RSS notification (recommended)'));
633: $oTpl->set('s', 'IMG_BUT_RSS', 'but_ok.gif');
634: $oTpl->set('s', 'LABEL_BUT_RSS', i18n('Enable notification'));
635: $oTpl->set('s', 'URL_RSS', $this->oSession->url('main.php?frame=4&area=mycontenido&do=activate_rss'));
636: $oTpl->set('s', 'NEWS_NOCONTENT', i18n('RSS notification is disabled'));
637: $oTpl->set("s", "DISPLAY_DISABLED", 'block');
638: }
639:
640: return $oTpl->generate('templates/standard/' . $this->aCfg['templates']['welcome_update'], 1);
641: }
642:
643: 644: 645: 646: 647: 648:
649: protected function renderRss($oTpl) {
650: if (!is_object($oTpl)) {
651: $oTpl = new cTemplate();
652: }
653:
654: if ($this->sRSSContent != '<rss></rss>') {
655: $doc = new cXmlReader();
656: $doc->load($this->sCacheDirectory . $this->sRSSFile);
657:
658: $maxFeedItems = 3;
659:
660: for ($iCnt = 0; $iCnt < $maxFeedItems; $iCnt++) {
661: $title = $doc->getXpathValue('*/channel/item/title', $iCnt);
662: $link = $doc->getXpathValue('*/channel/item/link', $iCnt);
663: $description = $doc->getXpathValue('*/channel/item/description', $iCnt);
664: $date = $doc->getXpathValue('*/channel/item/pubDate', $iCnt);
665:
666:
667:
668:
669: $title = utf8_encode($title);
670: $sText = utf8_encode($description);
671:
672: if (strlen($sText) > 150) {
673: $sText = cApiStrTrimAfterWord($sText, 150) . '...';
674: }
675:
676: $date = date(getEffectiveSetting("dateformat", "full", "Y-m-d H:i:s"), strtotime($date));
677:
678:
679: if ($iCnt == 0) {
680: $oTpl->set("d", "NEWS_NEWEST_CSS", "newest_news");
681: } else {
682: $oTpl->set("d", "NEWS_NEWEST_CSS", "");
683: }
684:
685: $oTpl->set("d", "NEWS_DATE", $date);
686: $oTpl->set("d", "NEWS_TITLE", $title);
687: $oTpl->set("d", "NEWS_TEXT", $sText);
688: $oTpl->set("d", "NEWS_URL", $link);
689: $oTpl->set("d", "LABEL_MORE", i18n('read more'));
690: $oTpl->next();
691: }
692:
693: if ($iCnt == 0) {
694: $oTpl->set("s", "NEWS_NOCONTENT", i18n("No RSS content available"));
695: $oTpl->set("s", "DISPLAY_DISABLED", 'block');
696: } else {
697: $oTpl->set("s", "NEWS_NOCONTENT", "");
698: $oTpl->set("s", "DISPLAY_DISABLED", 'none');
699: }
700: } else if ($this->bNoWritePermissions == true) {
701: $oTpl->set("s", "NEWS_NOCONTENT", i18n('Your webserver does not have write permissions for the directory /contenido/data/cache/!'));
702: } else {
703: $oTpl->set("s", "NEWS_NOCONTENT", i18n("No RSS content available"));
704: }
705:
706: return $oTpl;
707: }
708:
709: 710: 711: 712: 713: 714: 715:
716: private function fetchUrl($sUrl) {
717: if ($this->bVendorHostReachable != true) {
718: return false;
719: }
720:
721: $handler = cHttpRequest::getHttpRequest($this->sVendorHost . '/' . $sUrl);
722: $output = $handler->getRequest();
723:
724: if (!$output) {
725: $sErrorMessage = i18n('Unable to check for new updates!') . " " . i18n('Connection to contenido.org failed!');
726: $this->sErrorOutput = $this->renderOutput($sErrorMessage);
727: $this->bVendorHostReachable = false;
728: return false;
729: }
730:
731: return ($output != "") ? $output : false;
732: }
733:
734: 735: 736: 737: 738:
739: public function displayOutput() {
740: if (!$this->bEnableView) {
741: $sOutput = "";
742: } else if ($this->bNoWritePermissions == true) {
743: $sMessage = i18n('Your webserver does not have write permissions for the directory /contenido/data/cache/!');
744: $sOutput = $this->renderOutput($sMessage);
745: } else if (!$this->bEnableCheck) {
746: $sMessage = i18n('Update notification is disabled! For actual update information, please activate.');
747: $sOutput = $this->renderOutput($sMessage);
748: } else if ($this->sErrorOutput != "") {
749: $sOutput = $this->sErrorOutput;
750: } else if ($this->sVendorVersion == '') {
751: $sMessage = i18n('You have an unknown or unsupported version of CONTENIDO!');
752: $sOutput = $this->renderOutput($sMessage);
753: } else if ($this->sVendorVersion == "deprecated") {
754: $sMessage = sprintf(i18n("Your version of CONTENIDO is deprecated and not longer supported for any updates. Please update to a higher version! <br /> <a href='%s' class='blue' target='_blank'>Download now!</a>"), 'http://www.contenido.org');
755: $sOutput = $this->renderOutput($sMessage);
756: } else if ($this->checkPatchLevel() == "-1") {
757: $sVendorDownloadURL = $this->getDownloadURL();
758: $sMessage = sprintf(i18n("A new version of CONTENIDO is available! <br /> <a href='%s' class='blue' target='_blank'>Download %s now!</a>"), $sVendorDownloadURL, $this->sVendorVersion);
759: $sOutput = $this->renderOutput($sMessage);
760: } else if ($this->checkPatchLevel() == "1") {
761: $sMessage = sprintf(i18n('It seems to be that your version string was manipulated. CONTENIDO %s does not exist!'), CON_VERSION);
762: $sOutput = $this->renderOutput($sMessage);
763: } else {
764: $sMessage = i18n('Your version of CONTENIDO is up to date!');
765: $sOutput = $this->renderOutput($sMessage);
766: }
767:
768: return $sOutput;
769: }
770: }
771: