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