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