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