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