From 57708e31691e33a3c062e136f609a27d1c339d17 Mon Sep 17 00:00:00 2001 From: Jimmy Monin Date: Sun, 18 Sep 2016 11:03:26 +0200 Subject: [PATCH] Initial commit Functional, without SSO --- LICENSE | 674 ++++ README.md | 19 + conf/LBCAlerte.cron | 1 + conf/nginx.conf | 33 + manifest.json | 66 + scripts/backup | 25 + scripts/install | 83 + scripts/remove | 31 + scripts/restore | 40 + sources/CHANGELOG.txt | 125 + sources/LICENSE | 674 ++++ sources/README.md | 17 + sources/app/admin/init.php | 6 + sources/app/admin/scripts/add-user.php | 21 + sources/app/admin/scripts/delete-user.php | 15 + sources/app/admin/scripts/log.php | 12 + sources/app/admin/scripts/mail.php | 76 + sources/app/admin/scripts/proxy.php | 46 + sources/app/admin/scripts/storage.php | 102 + sources/app/admin/scripts/upgrade.php | 76 + sources/app/admin/scripts/users.php | 2 + sources/app/admin/views/add-user.phtml | 30 + sources/app/admin/views/delete-user.phtml | 6 + sources/app/admin/views/layout.phtml | 38 + sources/app/admin/views/log.phtml | 19 + sources/app/admin/views/mail.phtml | 84 + sources/app/admin/views/proxy.phtml | 62 + sources/app/admin/views/storage.phtml | 102 + sources/app/admin/views/upgrade.phtml | 54 + sources/app/admin/views/users.phtml | 23 + sources/app/default/scripts/login.php | 25 + sources/app/default/scripts/logout.php | 5 + sources/app/default/views/index.phtml | 11 + sources/app/default/views/layout.phtml | 38 + sources/app/default/views/login.phtml | 26 + sources/app/default/views/upgrade.phtml | 14 + sources/app/install/init.php | 11 + sources/app/install/scripts/index.php | 83 + sources/app/install/scripts/upgrade.php | 17 + sources/app/install/views/index.phtml | 116 + sources/app/install/views/upgrade.phtml | 5 + sources/app/mail/init.php | 9 + sources/app/mail/scripts/check.php | 561 +++ sources/app/mail/scripts/form-delete.php | 14 + sources/app/mail/scripts/form.php | 82 + sources/app/mail/scripts/index.php | 86 + sources/app/mail/scripts/reset.php | 8 + sources/app/mail/scripts/toggle_status.php | 12 + sources/app/mail/views/form-delete.phtml | 14 + sources/app/mail/views/form.phtml | 211 ++ sources/app/mail/views/index.phtml | 153 + sources/app/mail/views/mail-ad.phtml | 46 + sources/app/mail/views/reset.phtml | 13 + sources/app/models/Mail/Alert.php | 88 + sources/app/models/Storage/Alert.php | 14 + sources/app/models/Storage/Db/Alert.php | 141 + sources/app/models/Storage/Db/User.php | 102 + sources/app/models/Storage/File/Alert.php | 167 + sources/app/models/Storage/File/User.php | 145 + sources/app/models/Storage/User.php | 14 + sources/app/models/Updater.php | 186 + sources/app/models/User/User.php | 190 + sources/app/rss/scripts/index.php | 45 + sources/app/rss/scripts/refresh.php | 98 + sources/app/rss/views/index.phtml | 70 + sources/app/rss/views/rss-ad.phtml | 45 + sources/app/user/scripts/settings.php | 123 + sources/app/user/views/settings.phtml | 250 ++ sources/bootstrap.php | 188 + sources/check.php | 2 + sources/index.php | 95 + sources/lib/AdService/Ad.php | 287 ++ sources/lib/AdService/Exception.php | 8 + sources/lib/AdService/Filter.php | 188 + .../lib/AdService/Parser/AbstractParser.php | 19 + sources/lib/AdService/Parser/Lbc.php | 177 + sources/lib/AdService/Parser/Olx.php | 137 + sources/lib/AdService/Parser/Seloger.php | 125 + sources/lib/AdService/ParserFactory.php | 24 + .../SiteConfig/AbstractSiteConfig.php | 46 + sources/lib/AdService/SiteConfig/Lbc.php | 9 + sources/lib/AdService/SiteConfig/Olx.php | 11 + sources/lib/AdService/SiteConfig/Seloger.php | 11 + sources/lib/AdService/SiteConfigFactory.php | 33 + sources/lib/Auth/Abstract.php | 70 + sources/lib/Auth/Basic.php | 19 + sources/lib/Auth/Session.php | 45 + sources/lib/Config/Lite.php | 838 +++++ sources/lib/Config/Lite/Exception.php | 34 + .../Config/Lite/Exception/InvalidArgument.php | 35 + sources/lib/Config/Lite/Exception/Runtime.php | 36 + .../Config/Lite/Exception/UnexpectedValue.php | 36 + sources/lib/FeedWriter/ATOM.php | 38 + sources/lib/FeedWriter/COPYING | 674 ++++ sources/lib/FeedWriter/Feed.php | 869 +++++ sources/lib/FeedWriter/Item.php | 392 ++ sources/lib/FeedWriter/README | 41 + sources/lib/FeedWriter/RSS1.php | 37 + sources/lib/FeedWriter/RSS2.php | 37 + sources/lib/FeedWriter/composer.json | 41 + sources/lib/Http/Client/Abstract.php | 216 ++ sources/lib/Http/Client/Curl.php | 95 + sources/lib/Lbc/CategoryCollection.php | 86 + sources/lib/Log4php/Logger.php | 596 +++ sources/lib/Log4php/LoggerAppender.php | 286 ++ sources/lib/Log4php/LoggerAppenderPool.php | 98 + sources/lib/Log4php/LoggerAutoloader.php | 142 + sources/lib/Log4php/LoggerConfigurable.php | 116 + sources/lib/Log4php/LoggerConfigurator.php | 42 + sources/lib/Log4php/LoggerException.php | 28 + sources/lib/Log4php/LoggerFilter.php | 126 + sources/lib/Log4php/LoggerHierarchy.php | 257 ++ sources/lib/Log4php/LoggerLayout.php | 74 + sources/lib/Log4php/LoggerLevel.php | 256 ++ sources/lib/Log4php/LoggerLocationInfo.php | 103 + sources/lib/Log4php/LoggerLoggingEvent.php | 368 ++ sources/lib/Log4php/LoggerMDC.php | 88 + sources/lib/Log4php/LoggerNDC.php | 203 + sources/lib/Log4php/LoggerReflectionUtils.php | 152 + sources/lib/Log4php/LoggerRoot.php | 71 + .../Log4php/LoggerThrowableInformation.php | 68 + .../appenders/LoggerAppenderConsole.php | 103 + .../appenders/LoggerAppenderDailyFile.php | 130 + .../Log4php/appenders/LoggerAppenderEcho.php | 88 + .../Log4php/appenders/LoggerAppenderFile.php | 225 ++ .../appenders/LoggerAppenderFirePHP.php | 100 + .../Log4php/appenders/LoggerAppenderMail.php | 136 + .../appenders/LoggerAppenderMailEvent.php | 180 + .../appenders/LoggerAppenderMongoDB.php | 360 ++ .../Log4php/appenders/LoggerAppenderNull.php | 44 + .../Log4php/appenders/LoggerAppenderPDO.php | 282 ++ .../Log4php/appenders/LoggerAppenderPhp.php | 49 + .../appenders/LoggerAppenderRollingFile.php | 305 ++ .../appenders/LoggerAppenderSocket.php | 122 + .../appenders/LoggerAppenderSyslog.php | 303 ++ .../LoggerConfigurationAdapter.php | 39 + .../LoggerConfigurationAdapterINI.php | 299 ++ .../LoggerConfigurationAdapterPHP.php | 84 + .../LoggerConfigurationAdapterXML.php | 278 ++ .../LoggerConfiguratorDefault.php | 477 +++ .../Log4php/filters/LoggerFilterDenyAll.php | 56 + .../filters/LoggerFilterLevelMatch.php | 100 + .../filters/LoggerFilterLevelRange.php | 138 + .../filters/LoggerFilterStringMatch.php | 89 + .../Log4php/helpers/LoggerFormattingInfo.php | 54 + .../Log4php/helpers/LoggerOptionConverter.php | 226 ++ .../Log4php/helpers/LoggerPatternParser.php | 237 ++ sources/lib/Log4php/helpers/LoggerUtils.php | 123 + .../lib/Log4php/layouts/LoggerLayoutHtml.php | 214 ++ .../Log4php/layouts/LoggerLayoutPattern.php | 171 + .../layouts/LoggerLayoutSerialized.php | 55 + .../Log4php/layouts/LoggerLayoutSimple.php | 56 + .../lib/Log4php/layouts/LoggerLayoutTTCC.php | 201 + .../lib/Log4php/layouts/LoggerLayoutXml.php | 210 ++ .../pattern/LoggerPatternConverter.php | 131 + .../pattern/LoggerPatternConverterClass.php | 64 + .../pattern/LoggerPatternConverterCookie.php | 35 + .../pattern/LoggerPatternConverterDate.php | 91 + .../LoggerPatternConverterEnvironment.php | 35 + .../pattern/LoggerPatternConverterFile.php | 34 + .../pattern/LoggerPatternConverterLevel.php | 34 + .../pattern/LoggerPatternConverterLine.php | 35 + .../pattern/LoggerPatternConverterLiteral.php | 40 + .../LoggerPatternConverterLocation.php | 39 + .../pattern/LoggerPatternConverterLogger.php | 65 + .../pattern/LoggerPatternConverterMDC.php | 55 + .../pattern/LoggerPatternConverterMessage.php | 34 + .../pattern/LoggerPatternConverterMethod.php | 35 + .../pattern/LoggerPatternConverterNDC.php | 35 + .../pattern/LoggerPatternConverterNewLine.php | 34 + .../pattern/LoggerPatternConverterProcess.php | 34 + .../LoggerPatternConverterRelative.php | 36 + .../pattern/LoggerPatternConverterRequest.php | 35 + .../pattern/LoggerPatternConverterServer.php | 35 + .../pattern/LoggerPatternConverterSession.php | 35 + .../LoggerPatternConverterSessionID.php | 33 + .../LoggerPatternConverterSuperglobal.php | 102 + .../LoggerPatternConverterThrowable.php | 40 + .../lib/Log4php/renderers/LoggerRenderer.php | 36 + .../renderers/LoggerRendererDefault.php | 36 + .../renderers/LoggerRendererException.php | 36 + .../Log4php/renderers/LoggerRendererMap.php | 186 + sources/lib/Log4php/xml/log4php.dtd | 148 + .../lib/Message/Adapter/AdapterAbstract.php | 163 + .../lib/Message/Adapter/NotifyMyAndroid.php | 69 + sources/lib/Message/Adapter/Pushbullet.php | 57 + sources/lib/Message/Adapter/Pushover.php | 67 + sources/lib/Message/Adapter/SmsFreeMobile.php | 83 + sources/lib/Message/Adapter/SmsOvh.php | 142 + sources/lib/Message/AdapterFactory.php | 33 + sources/lib/PHPMailer/.gitignore | 4 + sources/lib/PHPMailer/.travis.yml | 20 + sources/lib/PHPMailer/LICENSE | 504 +++ sources/lib/PHPMailer/PHPMailerAutoload.php | 33 + sources/lib/PHPMailer/README.md | 134 + sources/lib/PHPMailer/changelog.md | 549 +++ sources/lib/PHPMailer/class.phpmailer.php | 3284 +++++++++++++++++ sources/lib/PHPMailer/class.pop3.php | 419 +++ sources/lib/PHPMailer/class.smtp.php | 958 +++++ sources/lib/PHPMailer/composer.json | 33 + sources/lib/PHPMailer/extras/EasyPeasyICS.php | 90 + .../lib/PHPMailer/extras/class.html2text.php | 696 ++++ sources/lib/PHPMailer/extras/htmlfilter.php | 861 +++++ .../lib/PHPMailer/extras/ntlm_sasl_client.php | 185 + .../PHPMailer/language/phpmailer.lang-ar.php | 26 + .../PHPMailer/language/phpmailer.lang-br.php | 26 + .../PHPMailer/language/phpmailer.lang-ca.php | 25 + .../PHPMailer/language/phpmailer.lang-ch.php | 25 + .../PHPMailer/language/phpmailer.lang-cz.php | 24 + .../PHPMailer/language/phpmailer.lang-de.php | 24 + .../PHPMailer/language/phpmailer.lang-dk.php | 25 + .../PHPMailer/language/phpmailer.lang-eo.php | 24 + .../PHPMailer/language/phpmailer.lang-es.php | 26 + .../PHPMailer/language/phpmailer.lang-et.php | 26 + .../PHPMailer/language/phpmailer.lang-fa.php | 25 + .../PHPMailer/language/phpmailer.lang-fi.php | 26 + .../PHPMailer/language/phpmailer.lang-fo.php | 26 + .../PHPMailer/language/phpmailer.lang-fr.php | 29 + .../PHPMailer/language/phpmailer.lang-gl.php | 26 + .../PHPMailer/language/phpmailer.lang-he.php | 25 + .../PHPMailer/language/phpmailer.lang-hu.php | 24 + .../PHPMailer/language/phpmailer.lang-it.php | 26 + .../PHPMailer/language/phpmailer.lang-ja.php | 26 + .../PHPMailer/language/phpmailer.lang-lt.php | 24 + .../PHPMailer/language/phpmailer.lang-nl.php | 24 + .../PHPMailer/language/phpmailer.lang-no.php | 24 + .../PHPMailer/language/phpmailer.lang-pl.php | 24 + .../PHPMailer/language/phpmailer.lang-ro.php | 26 + .../PHPMailer/language/phpmailer.lang-ru.php | 24 + .../PHPMailer/language/phpmailer.lang-se.php | 25 + .../PHPMailer/language/phpmailer.lang-sk.php | 25 + .../PHPMailer/language/phpmailer.lang-tr.php | 26 + .../PHPMailer/language/phpmailer.lang-uk.php | 24 + .../PHPMailer/language/phpmailer.lang-zh.php | 25 + .../language/phpmailer.lang-zh_cn.php | 26 + sources/others/.htaccess | 1 + sources/others/install/schema.php | 46 + sources/others/update/2.6.2.php | 13 + sources/others/update/2.6.php | 78 + sources/others/update/2.8.php | 29 + sources/others/update/3.1.php | 13 + sources/others/update/3.2.php | 30 + sources/others/update/update.php | 42 + sources/rss/index.php | 11 + sources/static/images/disable.png | Bin 0 -> 999 bytes sources/static/images/enable.png | Bin 0 -> 1022 bytes sources/static/images/home.png | Bin 0 -> 476 bytes sources/static/images/sort-asc.png | Bin 0 -> 302 bytes sources/static/images/sort-desc.png | Bin 0 -> 307 bytes sources/static/scripts.js | 19 + sources/static/styles.css | 341 ++ sources/var/.htaccess | 1 + sources/version.php | 3 + 253 files changed, 30787 insertions(+) create mode 100644 LICENSE create mode 100644 README.md create mode 100644 conf/LBCAlerte.cron create mode 100644 conf/nginx.conf create mode 100644 manifest.json create mode 100644 scripts/backup create mode 100644 scripts/install create mode 100644 scripts/remove create mode 100644 scripts/restore create mode 100644 sources/CHANGELOG.txt create mode 100644 sources/LICENSE create mode 100644 sources/README.md create mode 100644 sources/app/admin/init.php create mode 100644 sources/app/admin/scripts/add-user.php create mode 100644 sources/app/admin/scripts/delete-user.php create mode 100644 sources/app/admin/scripts/log.php create mode 100644 sources/app/admin/scripts/mail.php create mode 100644 sources/app/admin/scripts/proxy.php create mode 100644 sources/app/admin/scripts/storage.php create mode 100644 sources/app/admin/scripts/upgrade.php create mode 100644 sources/app/admin/scripts/users.php create mode 100644 sources/app/admin/views/add-user.phtml create mode 100644 sources/app/admin/views/delete-user.phtml create mode 100644 sources/app/admin/views/layout.phtml create mode 100644 sources/app/admin/views/log.phtml create mode 100644 sources/app/admin/views/mail.phtml create mode 100644 sources/app/admin/views/proxy.phtml create mode 100644 sources/app/admin/views/storage.phtml create mode 100644 sources/app/admin/views/upgrade.phtml create mode 100644 sources/app/admin/views/users.phtml create mode 100644 sources/app/default/scripts/login.php create mode 100644 sources/app/default/scripts/logout.php create mode 100644 sources/app/default/views/index.phtml create mode 100644 sources/app/default/views/layout.phtml create mode 100644 sources/app/default/views/login.phtml create mode 100644 sources/app/default/views/upgrade.phtml create mode 100644 sources/app/install/init.php create mode 100644 sources/app/install/scripts/index.php create mode 100644 sources/app/install/scripts/upgrade.php create mode 100644 sources/app/install/views/index.phtml create mode 100644 sources/app/install/views/upgrade.phtml create mode 100644 sources/app/mail/init.php create mode 100644 sources/app/mail/scripts/check.php create mode 100644 sources/app/mail/scripts/form-delete.php create mode 100644 sources/app/mail/scripts/form.php create mode 100644 sources/app/mail/scripts/index.php create mode 100644 sources/app/mail/scripts/reset.php create mode 100644 sources/app/mail/scripts/toggle_status.php create mode 100644 sources/app/mail/views/form-delete.phtml create mode 100644 sources/app/mail/views/form.phtml create mode 100644 sources/app/mail/views/index.phtml create mode 100644 sources/app/mail/views/mail-ad.phtml create mode 100644 sources/app/mail/views/reset.phtml create mode 100644 sources/app/models/Mail/Alert.php create mode 100644 sources/app/models/Storage/Alert.php create mode 100644 sources/app/models/Storage/Db/Alert.php create mode 100644 sources/app/models/Storage/Db/User.php create mode 100644 sources/app/models/Storage/File/Alert.php create mode 100644 sources/app/models/Storage/File/User.php create mode 100644 sources/app/models/Storage/User.php create mode 100644 sources/app/models/Updater.php create mode 100644 sources/app/models/User/User.php create mode 100644 sources/app/rss/scripts/index.php create mode 100644 sources/app/rss/scripts/refresh.php create mode 100644 sources/app/rss/views/index.phtml create mode 100644 sources/app/rss/views/rss-ad.phtml create mode 100644 sources/app/user/scripts/settings.php create mode 100644 sources/app/user/views/settings.phtml create mode 100644 sources/bootstrap.php create mode 100644 sources/check.php create mode 100644 sources/index.php create mode 100644 sources/lib/AdService/Ad.php create mode 100644 sources/lib/AdService/Exception.php create mode 100644 sources/lib/AdService/Filter.php create mode 100644 sources/lib/AdService/Parser/AbstractParser.php create mode 100644 sources/lib/AdService/Parser/Lbc.php create mode 100644 sources/lib/AdService/Parser/Olx.php create mode 100644 sources/lib/AdService/Parser/Seloger.php create mode 100644 sources/lib/AdService/ParserFactory.php create mode 100644 sources/lib/AdService/SiteConfig/AbstractSiteConfig.php create mode 100644 sources/lib/AdService/SiteConfig/Lbc.php create mode 100644 sources/lib/AdService/SiteConfig/Olx.php create mode 100644 sources/lib/AdService/SiteConfig/Seloger.php create mode 100644 sources/lib/AdService/SiteConfigFactory.php create mode 100644 sources/lib/Auth/Abstract.php create mode 100644 sources/lib/Auth/Basic.php create mode 100644 sources/lib/Auth/Session.php create mode 100644 sources/lib/Config/Lite.php create mode 100644 sources/lib/Config/Lite/Exception.php create mode 100644 sources/lib/Config/Lite/Exception/InvalidArgument.php create mode 100644 sources/lib/Config/Lite/Exception/Runtime.php create mode 100644 sources/lib/Config/Lite/Exception/UnexpectedValue.php create mode 100644 sources/lib/FeedWriter/ATOM.php create mode 100644 sources/lib/FeedWriter/COPYING create mode 100644 sources/lib/FeedWriter/Feed.php create mode 100644 sources/lib/FeedWriter/Item.php create mode 100644 sources/lib/FeedWriter/README create mode 100644 sources/lib/FeedWriter/RSS1.php create mode 100644 sources/lib/FeedWriter/RSS2.php create mode 100644 sources/lib/FeedWriter/composer.json create mode 100644 sources/lib/Http/Client/Abstract.php create mode 100644 sources/lib/Http/Client/Curl.php create mode 100644 sources/lib/Lbc/CategoryCollection.php create mode 100644 sources/lib/Log4php/Logger.php create mode 100644 sources/lib/Log4php/LoggerAppender.php create mode 100644 sources/lib/Log4php/LoggerAppenderPool.php create mode 100644 sources/lib/Log4php/LoggerAutoloader.php create mode 100644 sources/lib/Log4php/LoggerConfigurable.php create mode 100644 sources/lib/Log4php/LoggerConfigurator.php create mode 100644 sources/lib/Log4php/LoggerException.php create mode 100644 sources/lib/Log4php/LoggerFilter.php create mode 100644 sources/lib/Log4php/LoggerHierarchy.php create mode 100644 sources/lib/Log4php/LoggerLayout.php create mode 100644 sources/lib/Log4php/LoggerLevel.php create mode 100644 sources/lib/Log4php/LoggerLocationInfo.php create mode 100644 sources/lib/Log4php/LoggerLoggingEvent.php create mode 100644 sources/lib/Log4php/LoggerMDC.php create mode 100644 sources/lib/Log4php/LoggerNDC.php create mode 100644 sources/lib/Log4php/LoggerReflectionUtils.php create mode 100644 sources/lib/Log4php/LoggerRoot.php create mode 100644 sources/lib/Log4php/LoggerThrowableInformation.php create mode 100644 sources/lib/Log4php/appenders/LoggerAppenderConsole.php create mode 100644 sources/lib/Log4php/appenders/LoggerAppenderDailyFile.php create mode 100644 sources/lib/Log4php/appenders/LoggerAppenderEcho.php create mode 100644 sources/lib/Log4php/appenders/LoggerAppenderFile.php create mode 100644 sources/lib/Log4php/appenders/LoggerAppenderFirePHP.php create mode 100644 sources/lib/Log4php/appenders/LoggerAppenderMail.php create mode 100644 sources/lib/Log4php/appenders/LoggerAppenderMailEvent.php create mode 100644 sources/lib/Log4php/appenders/LoggerAppenderMongoDB.php create mode 100644 sources/lib/Log4php/appenders/LoggerAppenderNull.php create mode 100644 sources/lib/Log4php/appenders/LoggerAppenderPDO.php create mode 100644 sources/lib/Log4php/appenders/LoggerAppenderPhp.php create mode 100644 sources/lib/Log4php/appenders/LoggerAppenderRollingFile.php create mode 100644 sources/lib/Log4php/appenders/LoggerAppenderSocket.php create mode 100644 sources/lib/Log4php/appenders/LoggerAppenderSyslog.php create mode 100644 sources/lib/Log4php/configurators/LoggerConfigurationAdapter.php create mode 100644 sources/lib/Log4php/configurators/LoggerConfigurationAdapterINI.php create mode 100644 sources/lib/Log4php/configurators/LoggerConfigurationAdapterPHP.php create mode 100644 sources/lib/Log4php/configurators/LoggerConfigurationAdapterXML.php create mode 100644 sources/lib/Log4php/configurators/LoggerConfiguratorDefault.php create mode 100644 sources/lib/Log4php/filters/LoggerFilterDenyAll.php create mode 100644 sources/lib/Log4php/filters/LoggerFilterLevelMatch.php create mode 100644 sources/lib/Log4php/filters/LoggerFilterLevelRange.php create mode 100644 sources/lib/Log4php/filters/LoggerFilterStringMatch.php create mode 100644 sources/lib/Log4php/helpers/LoggerFormattingInfo.php create mode 100644 sources/lib/Log4php/helpers/LoggerOptionConverter.php create mode 100644 sources/lib/Log4php/helpers/LoggerPatternParser.php create mode 100644 sources/lib/Log4php/helpers/LoggerUtils.php create mode 100644 sources/lib/Log4php/layouts/LoggerLayoutHtml.php create mode 100644 sources/lib/Log4php/layouts/LoggerLayoutPattern.php create mode 100644 sources/lib/Log4php/layouts/LoggerLayoutSerialized.php create mode 100644 sources/lib/Log4php/layouts/LoggerLayoutSimple.php create mode 100644 sources/lib/Log4php/layouts/LoggerLayoutTTCC.php create mode 100644 sources/lib/Log4php/layouts/LoggerLayoutXml.php create mode 100644 sources/lib/Log4php/pattern/LoggerPatternConverter.php create mode 100644 sources/lib/Log4php/pattern/LoggerPatternConverterClass.php create mode 100644 sources/lib/Log4php/pattern/LoggerPatternConverterCookie.php create mode 100644 sources/lib/Log4php/pattern/LoggerPatternConverterDate.php create mode 100644 sources/lib/Log4php/pattern/LoggerPatternConverterEnvironment.php create mode 100644 sources/lib/Log4php/pattern/LoggerPatternConverterFile.php create mode 100644 sources/lib/Log4php/pattern/LoggerPatternConverterLevel.php create mode 100644 sources/lib/Log4php/pattern/LoggerPatternConverterLine.php create mode 100644 sources/lib/Log4php/pattern/LoggerPatternConverterLiteral.php create mode 100644 sources/lib/Log4php/pattern/LoggerPatternConverterLocation.php create mode 100644 sources/lib/Log4php/pattern/LoggerPatternConverterLogger.php create mode 100644 sources/lib/Log4php/pattern/LoggerPatternConverterMDC.php create mode 100644 sources/lib/Log4php/pattern/LoggerPatternConverterMessage.php create mode 100644 sources/lib/Log4php/pattern/LoggerPatternConverterMethod.php create mode 100644 sources/lib/Log4php/pattern/LoggerPatternConverterNDC.php create mode 100644 sources/lib/Log4php/pattern/LoggerPatternConverterNewLine.php create mode 100644 sources/lib/Log4php/pattern/LoggerPatternConverterProcess.php create mode 100644 sources/lib/Log4php/pattern/LoggerPatternConverterRelative.php create mode 100644 sources/lib/Log4php/pattern/LoggerPatternConverterRequest.php create mode 100644 sources/lib/Log4php/pattern/LoggerPatternConverterServer.php create mode 100644 sources/lib/Log4php/pattern/LoggerPatternConverterSession.php create mode 100644 sources/lib/Log4php/pattern/LoggerPatternConverterSessionID.php create mode 100644 sources/lib/Log4php/pattern/LoggerPatternConverterSuperglobal.php create mode 100644 sources/lib/Log4php/pattern/LoggerPatternConverterThrowable.php create mode 100644 sources/lib/Log4php/renderers/LoggerRenderer.php create mode 100644 sources/lib/Log4php/renderers/LoggerRendererDefault.php create mode 100644 sources/lib/Log4php/renderers/LoggerRendererException.php create mode 100644 sources/lib/Log4php/renderers/LoggerRendererMap.php create mode 100644 sources/lib/Log4php/xml/log4php.dtd create mode 100644 sources/lib/Message/Adapter/AdapterAbstract.php create mode 100644 sources/lib/Message/Adapter/NotifyMyAndroid.php create mode 100644 sources/lib/Message/Adapter/Pushbullet.php create mode 100644 sources/lib/Message/Adapter/Pushover.php create mode 100644 sources/lib/Message/Adapter/SmsFreeMobile.php create mode 100644 sources/lib/Message/Adapter/SmsOvh.php create mode 100644 sources/lib/Message/AdapterFactory.php create mode 100644 sources/lib/PHPMailer/.gitignore create mode 100644 sources/lib/PHPMailer/.travis.yml create mode 100644 sources/lib/PHPMailer/LICENSE create mode 100644 sources/lib/PHPMailer/PHPMailerAutoload.php create mode 100644 sources/lib/PHPMailer/README.md create mode 100644 sources/lib/PHPMailer/changelog.md create mode 100644 sources/lib/PHPMailer/class.phpmailer.php create mode 100644 sources/lib/PHPMailer/class.pop3.php create mode 100644 sources/lib/PHPMailer/class.smtp.php create mode 100644 sources/lib/PHPMailer/composer.json create mode 100644 sources/lib/PHPMailer/extras/EasyPeasyICS.php create mode 100644 sources/lib/PHPMailer/extras/class.html2text.php create mode 100644 sources/lib/PHPMailer/extras/htmlfilter.php create mode 100644 sources/lib/PHPMailer/extras/ntlm_sasl_client.php create mode 100644 sources/lib/PHPMailer/language/phpmailer.lang-ar.php create mode 100644 sources/lib/PHPMailer/language/phpmailer.lang-br.php create mode 100644 sources/lib/PHPMailer/language/phpmailer.lang-ca.php create mode 100644 sources/lib/PHPMailer/language/phpmailer.lang-ch.php create mode 100644 sources/lib/PHPMailer/language/phpmailer.lang-cz.php create mode 100644 sources/lib/PHPMailer/language/phpmailer.lang-de.php create mode 100644 sources/lib/PHPMailer/language/phpmailer.lang-dk.php create mode 100644 sources/lib/PHPMailer/language/phpmailer.lang-eo.php create mode 100644 sources/lib/PHPMailer/language/phpmailer.lang-es.php create mode 100644 sources/lib/PHPMailer/language/phpmailer.lang-et.php create mode 100644 sources/lib/PHPMailer/language/phpmailer.lang-fa.php create mode 100644 sources/lib/PHPMailer/language/phpmailer.lang-fi.php create mode 100644 sources/lib/PHPMailer/language/phpmailer.lang-fo.php create mode 100644 sources/lib/PHPMailer/language/phpmailer.lang-fr.php create mode 100644 sources/lib/PHPMailer/language/phpmailer.lang-gl.php create mode 100644 sources/lib/PHPMailer/language/phpmailer.lang-he.php create mode 100644 sources/lib/PHPMailer/language/phpmailer.lang-hu.php create mode 100644 sources/lib/PHPMailer/language/phpmailer.lang-it.php create mode 100644 sources/lib/PHPMailer/language/phpmailer.lang-ja.php create mode 100644 sources/lib/PHPMailer/language/phpmailer.lang-lt.php create mode 100644 sources/lib/PHPMailer/language/phpmailer.lang-nl.php create mode 100644 sources/lib/PHPMailer/language/phpmailer.lang-no.php create mode 100644 sources/lib/PHPMailer/language/phpmailer.lang-pl.php create mode 100644 sources/lib/PHPMailer/language/phpmailer.lang-ro.php create mode 100644 sources/lib/PHPMailer/language/phpmailer.lang-ru.php create mode 100644 sources/lib/PHPMailer/language/phpmailer.lang-se.php create mode 100644 sources/lib/PHPMailer/language/phpmailer.lang-sk.php create mode 100644 sources/lib/PHPMailer/language/phpmailer.lang-tr.php create mode 100644 sources/lib/PHPMailer/language/phpmailer.lang-uk.php create mode 100644 sources/lib/PHPMailer/language/phpmailer.lang-zh.php create mode 100644 sources/lib/PHPMailer/language/phpmailer.lang-zh_cn.php create mode 100644 sources/others/.htaccess create mode 100644 sources/others/install/schema.php create mode 100644 sources/others/update/2.6.2.php create mode 100644 sources/others/update/2.6.php create mode 100644 sources/others/update/2.8.php create mode 100644 sources/others/update/3.1.php create mode 100644 sources/others/update/3.2.php create mode 100644 sources/others/update/update.php create mode 100644 sources/rss/index.php create mode 100644 sources/static/images/disable.png create mode 100644 sources/static/images/enable.png create mode 100644 sources/static/images/home.png create mode 100644 sources/static/images/sort-asc.png create mode 100644 sources/static/images/sort-desc.png create mode 100644 sources/static/scripts.js create mode 100644 sources/static/styles.css create mode 100644 sources/var/.htaccess create mode 100644 sources/version.php diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..ef7e7ef --- /dev/null +++ b/LICENSE @@ -0,0 +1,674 @@ +GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + {one line to give the program's name and a brief idea of what it does.} + Copyright (C) {year} {name of author} + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + {project} Copyright (C) {year} {fullname} + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/README.md b/README.md new file mode 100644 index 0000000..9aa72ae --- /dev/null +++ b/README.md @@ -0,0 +1,19 @@ +LBCAlerte for YunoHost +--------------------- + +Logiciel pour être alerté de nouvelles annonces par mail, flux RSS et SMS sur Leboncoin et d'autres sites d'annonces. + +**Version incluse:** 3.2 + +## Limitations + +Application en français uniquement. + +Ne fonctionne pas avec le SSO (comptes admin/utilisateurs séparés). + + +## Links + + * Report a bug: https://github.com/JimboJoe/LBCAlerte_ynh/issues + * LBCAlerte website: https://alerte.ilatumi.org/ + * YunoHost website: https://yunohost.org/ diff --git a/conf/LBCAlerte.cron b/conf/LBCAlerte.cron new file mode 100644 index 0000000..0fd6ff1 --- /dev/null +++ b/conf/LBCAlerte.cron @@ -0,0 +1 @@ +*/5 * * * * www-data /usr/bin/php -f #DESTDIR#/check.php \ No newline at end of file diff --git a/conf/nginx.conf b/conf/nginx.conf new file mode 100644 index 0000000..8c76342 --- /dev/null +++ b/conf/nginx.conf @@ -0,0 +1,33 @@ +location YNH_WWW_PATH { + + # Path to source + alias YNH_WWW_ALIAS ; + + # Example PHP configuration (remove if not used) + index index.php; + + # Common parameter to increase upload size limit in conjuction with dedicated php-fpm file + #client_max_body_size 50M; + + try_files $uri $uri/ index.php; + location ~ [^/]\.php(/|$) { + fastcgi_split_path_info ^(.+?\.php)(/.*)$; + fastcgi_pass unix:/var/run/php5-fpm.sock; + + # Filename to be changed if dedicated php-fpm process is required + # This is to be used INSTEAD of line above + # Don't forget to adjust scripts install/upgrade/remove/backup accordingly + # + #fastcgi_pass unix:/var/run/php5-fpm-YNH_WWW_APP.sock; + + fastcgi_index index.php; + include fastcgi_params; + fastcgi_param REMOTE_USER $remote_user; + fastcgi_param PATH_INFO $fastcgi_path_info; + fastcgi_param SCRIPT_FILENAME $request_filename; + } + # PHP configuration end + + # Include SSOWAT user panel. + include conf.d/yunohost_panel.conf.inc; +} diff --git a/manifest.json b/manifest.json new file mode 100644 index 0000000..effb9eb --- /dev/null +++ b/manifest.json @@ -0,0 +1,66 @@ +{ + "name": "LBCAlerte", + "id": "LBCAlerte", + "packaging_format": 1, + "description": { + "en": "LBCAlerte package for YunoHost.", + "fr": "Application LBCAlerte pour YunoHost." + }, + "url": "https://alerte.ilatumi.org/", + "license": "free", + "maintainer": { + "name": "JimboJoe", + "email": "jimmy@monin.net", + "url": "" + }, + "requirements": { + "yunohost": ">> 2.4.0" + }, + "multi_instance": true, + "services": [ + "nginx", + "php5-fpm", + "mysql" + ], + "arguments": { + "install" : [ + { + "name": "domain", + "type": "domain", + "ask": { + "en": "Choose a domain name for LBCAlerte", + "fr": "Choisissez un nom de domaine pour LBCAlerte" + }, + "example": "example.com" + }, + { + "name": "path", + "type": "path", + "ask": { + "en": "Choose a path for LBCAlerte", + "fr": "Choisissez un chemin pour LBCAlerte" + }, + "example": "/LBCAlerte", + "default": "/LBCAlerte" + }, + { + "name": "password", + "type": "password", + "ask": { + "en": "Password for admin account", + "fr": "Mot de passe compte admin" + }, + "example": "password" + }, + { + "name": "is_public", + "type": "boolean", + "ask": { + "en": "Is it a public application?", + "fr": "Est-ce une application publique ?" + }, + "default": true + } + ] + } +} diff --git a/scripts/backup b/scripts/backup new file mode 100644 index 0000000..f4e0a02 --- /dev/null +++ b/scripts/backup @@ -0,0 +1,25 @@ +#!/bin/bash + +# Exit on command errors and treat unset variables as an error +set -eu + +# See comments in install script +app=$YNH_APP_INSTANCE_NAME + +# Source YunoHost helpers +source /usr/share/yunohost/helpers + +# Backup sources & data +# Note: the last argument is where to save this path, see the restore script. +ynh_backup "/var/www/${app}" "sources" + +# Dump the database +dbname=$app +dbuser=$app +dbpass=$(ynh_app_setting_get "$app" mysqlpwd) +mysqldump -u "$dbuser" -p"$dbpass" --no-create-db "$dbname" > ./dump.sql + +# Copy NGINX and cron configuration +domain=$(ynh_app_setting_get "$app" domain) +ynh_backup "/etc/nginx/conf.d/${domain}.d/${app}.conf" "nginx.conf" +ynh_backup "/etc/cron.d/${app}" "conf/cron" diff --git a/scripts/install b/scripts/install new file mode 100644 index 0000000..c62bda6 --- /dev/null +++ b/scripts/install @@ -0,0 +1,83 @@ +#!/bin/bash + +# Exit on command errors and treat unset variables as an error +set -eu + +# This is a multi-instance app, meaning it can be installed several times independently +# The id of the app as stated in the manifest is available as $YNH_APP_ID +# The instance number is available as $YNH_APP_INSTANCE_NUMBER (equals "1", "2", ...) +# The app instance name is available as $YNH_APP_INSTANCE_NAME +# - the first time the app is installed, YNH_APP_INSTANCE_NAME = ynhexample +# - the second time the app is installed, YNH_APP_INSTANCE_NAME = ynhexample__2 +# - ynhexample__{N} for the subsequent installations, with N=3,4, ... +# The app instance name is probably what you are interested the most, since this is +# guaranteed to be unique. This is a good unique identifier to define installation path, +# db names, ... +app=$YNH_APP_INSTANCE_NAME + +# Retrieve arguments +domain=$YNH_APP_ARG_DOMAIN +path=$YNH_APP_ARG_PATH +password=$YNH_APP_ARG_PASSWORD +is_public=$YNH_APP_ARG_IS_PUBLIC + +# Source YunoHost helpers +source /usr/share/yunohost/helpers + +# Save app settings +ynh_app_setting_set "$app" is_public "$is_public" + +# Check domain/path availability +sudo yunohost app checkurl "${domain}${path}" -a "$app" \ + || ynh_die "Path not available: ${domain}${path}" + +# Copy source files +src_path=/var/www/$app +sudo mkdir -p $src_path/var +sudo cp -a ../sources/. $src_path + +# Set permissions to app files +# you may need to make some file and/or directory writeable by www-data (nginx user) +sudo chown -R root: $src_path + +# Generate MySQL password and create database +dbuser=$app +dbname=$app +dbpass=$(ynh_string_random 12) +ynh_app_setting_set "$app" mysqlpwd "$dbpass" +ynh_mysql_create_db "$dbname" "$dbuser" "$dbpass" + +# Load initial SQL into the new database +#ynh_mysql_connect_as "$dbuser" "$dbpass" "$dbname" < "../sources/sql/mysql.init.sql" + +# Modify Nginx configuration file and copy it to Nginx conf directory +nginx_conf=../conf/nginx.conf +sed -i "s@YNH_WWW_PATH@$path@g" $nginx_conf +sed -i "s@YNH_WWW_ALIAS@$src_path/@g" $nginx_conf +# If a dedicated php-fpm process is used: +# Don't forget to modify ../conf/nginx.conf accordingly or your app will not work! +# sed -i "s@YNH_WWW_APP@$app@g" $nginx_conf +sudo cp $nginx_conf /etc/nginx/conf.d/$domain.d/$app.conf + +# Set permissions and reload nginx (needed at this stage for the PHP piwigo installation process) +sudo service nginx reload +sudo yunohost app setting "$app" unprotected_uris -v "/" +sudo yunohost app ssowatconf + + +# Configure LBCAlerte via curl +sleep 2 +curl --resolve $domain:443:127.0.0.1 -kL -X POST https://$domain$path/index.php?mod=install --data "password=$password&confirmPassword=$password&type=db&db[host]=localhost&db[user]=$dbuser&db[password]=$dbpass&db[dbname]=$dbname" > /tmp/LBCAlerte-install.log 2>&1 + +#protect URIs if private +if [ $is_public -eq 0 ]; +then + sudo yunohost app setting "$app" -d unprotected_uris + sudo yunohost app setting "$app" protected_uris -v "/" +fi + +# Add cron job +cron_path="/etc/cron.d/$app" +sed -i "s@#DESTDIR#@${src_path}@g" ../conf/LBCAlerte.cron +sudo cp ../conf/LBCAlerte.cron "$cron_path" +sudo chmod 644 "$cron_path" \ No newline at end of file diff --git a/scripts/remove b/scripts/remove new file mode 100644 index 0000000..09385e5 --- /dev/null +++ b/scripts/remove @@ -0,0 +1,31 @@ +#!/bin/bash + +# See comments in install script +app=$YNH_APP_INSTANCE_NAME + +# Source YunoHost helpers +source /usr/share/yunohost/helpers + +# Retrieve app settings +domain=$(ynh_app_setting_get "$app" domain) + +# Remove sources +sudo rm -rf /var/www/$app + +# Remove nginx configuration file +sudo rm -f /etc/nginx/conf.d/$domain.d/$app.conf + +### PHP (remove if not used) ### +# If a dedicated php-fpm process is used: +# sudo rm -f /etc/php5/fpm/pool.d/$app.conf +# sudo service php5-fpm reload +### PHP end ### + +# Drop MySQL database and user +dbname=$app +dbuser=$app +ynh_mysql_drop_db "$dbname" || true +ynh_mysql_drop_user "$dbuser" || true + +# Reload nginx service +sudo service nginx reload diff --git a/scripts/restore b/scripts/restore new file mode 100644 index 0000000..4da8cc3 --- /dev/null +++ b/scripts/restore @@ -0,0 +1,40 @@ +#!/bin/bash + +# Exit on command errors and treat unset variables as an error +set -eu + +# See comments in install script +app=$YNH_APP_INSTANCE_NAME + +# Source YunoHost helpers +source /usr/share/yunohost/helpers + +# Retrieve old app settings +domain=$(ynh_app_setting_get "$app" domain) +path=$(ynh_app_setting_get "$app" path) + +# Check domain/path availability +sudo yunohost app checkurl "${domain}${path}" -a "$app" \ + || ynh_die "Path not available: ${domain}${path}" + +# Restore sources & data +src_path="/var/www/${app}" +sudo cp -a ./sources "$src_path" + +# Restore permissions to app files +# you may need to make some file and/or directory writeable by www-data (nginx user) +sudo chown -R root: "$src_path" + +# Create and restore the database +dbname=$app +dbuser=$app +dbpass=$(ynh_app_setting_get "$app" mysqlpwd) +ynh_mysql_create_db "$dbname" "$dbuser" "$dbpass" +ynh_mysql_connect_as "$dbuser" "$dbpass" "$dbname" < ./dump.sql + +# Restore NGINX and cron configuration +sudo cp -a ./nginx.conf "/etc/nginx/conf.d/${domain}.d/${app}.conf" +sudo cp -a ./conf/cron "/etc/cron.d/${app}" + +# Restart webserver +sudo service nginx reload diff --git a/sources/CHANGELOG.txt b/sources/CHANGELOG.txt new file mode 100644 index 0000000..434ecd1 --- /dev/null +++ b/sources/CHANGELOG.txt @@ -0,0 +1,125 @@ + +## Version 3.2 + + * correction: si CURLOPT_FOLLOWLOCATION désactivé, suivre manuellement les redirections. + * correction: utiliser portail.free.fr pour le test de connexion distant. + * correction: utiliser le protocol HTTPs pour Leboncoin et alerte.ilatumi.org. + * correction: supprimer le nom du fichier dans l'URL de base de LBCAlerte. + * correction: crash possible si URL invalide. + * correction: amélioration des infos de log pour la tâche cron. + +## Version 3.1 + + * ajout: prise en charge de SeLoger.com. + * correction: last_id contient maintenant l'ID de la dernière annonce et max_id l'ID max trouvé. + * correction: indiquer l'URL mobile seulement si disponible pour le site d'annonce. + * correction: si pas de date de publication fournie, on désactive la détection des annonces remontées. + * correction: date optionnelle dans les flux RSS. + * correction: date mise à jour du parser pour le nouveau site LBC. + +## Version 3.0 + + * ajout: liens modifier, supprimer et activer/désactiver dans les mails d'alerte. + * ajout: bouton permettant de réinitialiser les alertes afin de recevoir toutes les dernières annonces. + * correction: amélioration de la mise à jour automatique. + +## Version 2.9 + + * correction: prise en charge du protocole HTTPS pour les URL Leboncoin. + * correction: utiliser le protocole HTTPS pour les URL externe (Google) (fixes 34). + +## Version 2.8 + + * ajout: en charge de NotityMyAndroid et Pushover pour les alertes. + * correction: mise en forme de la liste des catégories des formulaires (fixes #29) + * correction: ne pas échapper les caractères spéciaux pour l'URL de recherche dans le flux RSS (fixes #27) + * correction: indiquer le nombre de nouelles annonces dans les mails (fixes #24) + * correction: lien vers la version mobile de l'annonce dans le mail d'alerte (fixes #17). + +## Version 2.7 + + * ajout: message "Pas de photo disponible." dans les flux RSS lorsqu'une annonce n'a pas de photo. + * ajout: mise en place d'une nouvelle structure de classe pour permettre l'ajout d'autre site d'annonce dans le système. + * ajout: prise en charge du site d'annonce Olx.ua + * correction: supprimer le trie par prix lors de l'enregistrement des URLs (fixes #30). + * correction: configurer correctement l'envoi des mails (fixes #28). + * correction: avertissement sur l'utilisation du filtre sur catégorie (fixes #22). + * correction: ne pas activer CURLOPT_FOLLOWLOCATION si open_basedir définit (fixes #18). + * correction: le fichier lock ne fait pas son boulot et des tâches parallèles s'exécutaient. + * correction: enlever la date pour l'ID des éléments de flux (risque de doublon pour Olx) + * correction: utiliser \AdService\SiteConfigFactory::factory pour valider l'URL de recherche. + * correction: mise en forme du rendu des alertes. + * correction: orthographe. + * supprimer: système de daemon car non utilisé. + +## Version 2.6.2 + + * ajout: oublie d'ajout d'une colonne en BDD. + * correction: modification du parser d'annonce suite à modif sur Leboncoin. + +## Version 2.6.1 + + * correction: crash mise à jour. + +## Version 2.6 + + * ajout: option pour ne pas recevoir les annonces remontées (évite les doublons) + * ajout: prise en charge des notifications SMS OVH et Pushbullet. + * correction: installation bloquée par le système de mise à jour automatique. + * correction: définir la propriété Sender de PHPMailer afin d'éviter de passer en spam. + +## Version 2.5 + + * ajout: gestion de la mise à jour automatique. + * ajout: possibilité d'utiliser une base de données (MySQL) pour stocker les informations. + * ajout: configuration du stockage des données dans l'administration. + * ajout: possibilité de changer le type de stockage avec prise en charge d'une migration (seulement fichier > BDD). + * correction: ne pas logger "Contrôle de l'alerte " si l'alerte n'a pas besoin d'être analysée. + * correction: masquer les colonnes "Prix" et "Villes" du tableau d'alerte si non utilisées. + * correction: refonte graphique et simplification des formulaires + correctons divers bugs. + +## Version 2.4.1 + + * correction: faute orthographe dans nom de méthode. + +## Version 2.4 + + * ajout: notification par SMS pour les abonnés Free Mobile. + * ajout: numéroter les lignes dans le tableau des alertes mails. + * ajout: filtre multi départements en complément du filtre multi villes (thx @greenchap). + * ajout: trie des alertes par email ou par titre. + * ajout: configurer l'UTF-8 pour les fonctions mb_* + * ajout: prise en charge de certain proxy web (expérimental). + * amélioration: formulaire d'ajout et de modification d'alerte améliorer. + * correction: conflit de session possible si installation en parallèle avec d'autre application (sous dossier). + * correction: trier les groupes par ordre alphabétique. + * correction: corriger la définition du type de proxy. + * utiliser file_get_contents/file_put_contents au lieu de "rename" (problème de droit). + +## Version 2.3 + + * correction: mise en place de rotation de log (5*3Mo max). + * correction: paramètre invalide pour la fonction date. + * correction: tester la connexion même si aucun proxy spécifié. + +## Version 2.2 + + * ajout: possibilité de spécifier plusieurs adresses de destinataire pour les alertes mails. + * amélioration de l'affichage de la liste des alertes. + * correction: crash de check.php si une adresse mail est invalide. + * correction: vérifier la prise en charge des SIGNAL. + +## Version 2.1 + + * correction: check.php crash sur variable inéxistante. + +## Version 2.0 + + * fusion de LBCRss et LBCMail. + * ajout: installateur. + * ajout: panneau d'administration (gestion proxy, configuration envoi de mail, etc.). + * ajout: gestion de compte utilisateur. + * ajout: que ce soit pour les flux RSS ou les alertes mail, une identification est requise. + * ajout: possibilité de recevoir les nouvelles annonces dans des mails séparés. + * ajout: nouveau système de contrôle des alertes. + * ajout: inclure l'URL de recherche dans le mail. diff --git a/sources/LICENSE b/sources/LICENSE new file mode 100644 index 0000000..ef7e7ef --- /dev/null +++ b/sources/LICENSE @@ -0,0 +1,674 @@ +GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + {one line to give the program's name and a brief idea of what it does.} + Copyright (C) {year} {name of author} + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + {project} Copyright (C) {year} {fullname} + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/sources/README.md b/sources/README.md new file mode 100644 index 0000000..a0b6e05 --- /dev/null +++ b/sources/README.md @@ -0,0 +1,17 @@ +LBCAlerte +====== + +Alerte mail et RSS pour Leboncoin.fr + +## Introduction + +À quoi sert cette application ? +* recevoir des alertes mails sur vos recherches. +* générer des flux RSS pour vos recherches. +* recevoir des alertes SMS pour les abonnés Free Mobile. +* compléter vos recherches avec des filtres supplémentaires (multi-categories, multi-villes, etc.). + +## Documentation + +Pour l'installation et la configuration, une documentation est disponible sur le site officiel : +https://alerte.ilatumi.org diff --git a/sources/app/admin/init.php b/sources/app/admin/init.php new file mode 100644 index 0000000..b52455d --- /dev/null +++ b/sources/app/admin/init.php @@ -0,0 +1,6 @@ +getUsername() != "admin") { + header("HTTP/1.1 403 Forbidden"); + exit; +} \ No newline at end of file diff --git a/sources/app/admin/scripts/add-user.php b/sources/app/admin/scripts/add-user.php new file mode 100644 index 0000000..6d2227f --- /dev/null +++ b/sources/app/admin/scripts/add-user.php @@ -0,0 +1,21 @@ +setUsername(trim($_POST["username"])); + } + if (empty($_POST["password"])) { + $errors["password"] = "Veuillez indiquer un mot de passe."; + } elseif (empty($_POST["password"]) || $_POST["password"] != $_POST["confirmPassword"]) { + $errors["confirmPassword"] = "Les deux mots de passe ne correspondent pas."; + } + if (empty($errors)) { + $user->setPassword(sha1($_POST["password"])); + $userStorage->save($user); + header("LOCATION: ?mod=admin&a=users"); + exit; + } +} \ No newline at end of file diff --git a/sources/app/admin/scripts/delete-user.php b/sources/app/admin/scripts/delete-user.php new file mode 100644 index 0000000..f0a14bb --- /dev/null +++ b/sources/app/admin/scripts/delete-user.php @@ -0,0 +1,15 @@ +fetchByUsername($_GET["username"])) { + header("LOCATION: ?mod=admin&a=users"); + exit; +} +if ($_SERVER["REQUEST_METHOD"] == "POST") { + $userStorage->delete($user); + $configAlert = DOCUMENT_ROOT."/var/configs/".$user->getUsername().".csv"; + if (is_file($configAlert)) { + unlink($configAlert); + } + header("LOCATION: ?mod=admin&a=users"); + exit; +} \ No newline at end of file diff --git a/sources/app/admin/scripts/log.php b/sources/app/admin/scripts/log.php new file mode 100644 index 0000000..f920503 --- /dev/null +++ b/sources/app/admin/scripts/log.php @@ -0,0 +1,12 @@ + 200) { + $lines = array_slice($lines, count($lines)-200); + } +} \ No newline at end of file diff --git a/sources/app/admin/scripts/mail.php b/sources/app/admin/scripts/mail.php new file mode 100644 index 0000000..80e1517 --- /dev/null +++ b/sources/app/admin/scripts/mail.php @@ -0,0 +1,76 @@ + "", "port" => "", + "username" => "", "password" => "", + "secure" => "", + "from" => "", + "testMail" => "" +); +if ($config->hasSection("mailer")) { + if ($smtp = $config->get("mailer", "smtp", array())) { + $options = array_merge($options, $smtp); + } + if ($from = $config->get("mailer", "from", null)) { + $options["from"] = $from; + } +} +if ($_SERVER["REQUEST_METHOD"] == "POST") { + $options = array_merge($options, array_map("trim", $_POST)); + if (isset($_POST["testSMTP"])) { + require_once "PHPMailer/class.phpmailer.php"; + $mailer = new PHPMailer($exceptions=true); + $mailer->setLanguage("fr", DOCUMENT_ROOT."/lib/PHPMailer/language/"); + $mailer->CharSet = "utf-8"; + if (!empty($options["host"])) { + $mailer->Host = $options["host"]; + $mailer->isSMTP(); + } + if (!empty($options["port"])) { + $mailer->Port = $options["port"]; + $mailer->isSMTP(); + } + if (!empty($options["username"])) { + $mailer->SMTPAuth = true; + $mailer->Username = $options["username"]; + } + if (!empty($options["password"])) { + $mailer->SMTPAuth = true; + $mailer->Password = $options["password"]; + } + if (!empty($options["secure"])) { + $mailer->SMTPSecure = $options["secure"]; + } + if (!empty($options["from"])) { + $mailer->Sender = $options["from"]; + $mailer->From = $options["from"]; + } + if (empty($_POST["testMail"])) { + $errors["testMail"] = "Indiquez une adresse e-mail pour l'envoi du test."; + } else { + $mailer->clearAddresses(); + $mailer->addAddress($_POST["testMail"]); + if ($options["from"]) { + $mailer->FromName = $options["from"]; + } + $mailer->Subject = "Test d'envoi de mail"; + $mailer->Body = "Bravo.\nVotre configuration mail est validée."; + try { + $mailer->send(); + $testSended = true; + } catch (phpmailerException $e) { + $testError = $e->getMessage(); + } + } + } else { + $config->set("mailer", "smtp", array( + "host" => $options["host"], "port" => $options["port"], + "username" => $options["username"], "password" => $options["password"], + "secure" => $options["secure"] + )); + $config->set("mailer", "from", $options["from"]); + $config->save(); + header("LOCATION: ?mod=admin&a=mail"); + exit; + } +} \ No newline at end of file diff --git a/sources/app/admin/scripts/proxy.php b/sources/app/admin/scripts/proxy.php new file mode 100644 index 0000000..1a8ce8c --- /dev/null +++ b/sources/app/admin/scripts/proxy.php @@ -0,0 +1,46 @@ + $config->get("proxy", "ip", ""), + "proxy_port" => $config->get("proxy", "port", ""), + "proxy_user" => $config->get("proxy", "user", ""), + "proxy_password" => $config->get("proxy", "password", "") +); +if ($_SERVER["REQUEST_METHOD"] == "POST") { + $options = array_merge(array( + "proxy_ip" => "", + "proxy_port" => "", + "proxy_user" => "" + ), array_map("trim", $_POST)); + if (isset($options["proxy_ip"])) { + $options["proxy_ip"] = $options["proxy_ip"]; + if (isset($options["proxy_port"])) { + $options["proxy_port"] = $options["proxy_port"]; + } + } + if (isset($_POST["testProxy"])) { + $client->setProxyIp($options["proxy_ip"]) + ->setProxyPort($options["proxy_port"]) + ->setProxyUser($options["proxy_user"]); + if (!empty($options["proxy_password"])) { + $client->setProxyPassword($options["proxy_password"]); + } + $errors["test"] = array(); + if (false === $client->request("http://portail.free.fr")) { + $errors["test"]["site"] = $client->getError(); + } + if (false === $client->request("https://www.leboncoin.fr")) { + $errors["test"]["lbc"] = $client->getError(); + } + } else { + $config->set("proxy", "ip", $options["proxy_ip"]); + $config->set("proxy", "port", $options["proxy_port"]); + $config->set("proxy", "user", $options["proxy_user"]); + if (!empty($options["proxy_password"])) { + $config->set("proxy", "password", $options["proxy_password"]); + } + $config->save(); + header("LOCATION: ?mod=admin&a=proxy"); + exit; + } +} \ No newline at end of file diff --git a/sources/app/admin/scripts/storage.php b/sources/app/admin/scripts/storage.php new file mode 100644 index 0000000..7b25d09 --- /dev/null +++ b/sources/app/admin/scripts/storage.php @@ -0,0 +1,102 @@ +get("storage", "type", "files"); + +$currentStorage = array( + "type" => $config->get("storage", "type", "files"), + "options" => $config->get("storage", "options", array()) +); + +$errors = array(); +if ($_SERVER["REQUEST_METHOD"] == "POST") { + + if (!isset($_POST["type"]) || !trim($_POST["type"]) + || !in_array($_POST["type"], array("files", "db"))) { + $errors["type"] = "Type de stockage invalide."; + } + + $currentStorage = array( + "type" => isset($_POST["type"]) ? $_POST["type"] : $currentStorage["type"], + "options" => array_merge($currentStorage["options"], + isset($_POST["options"]) && is_array($_POST["options"]) ? $_POST["options"] : array()) + ); + + if ($_POST["type"] == "db") { + if (!isset($_POST["options"]["password"])) { + $_POST["options"]["password"] = ""; + } + if (empty($_POST["options"]["host"])) { + $errors["host"] = "Nom d'hôte invalide."; + } + if (empty($_POST["options"]["user"])) { + $errors["user"] = "Spécifiez un nom d'utilisateur."; + } + if (empty($_POST["options"]["dbname"])) { + $errors["dbname"] = "Spécifiez une base de données."; + } + if (!empty($_POST["options"]["user"]) && !empty($_POST["options"]["dbname"])) { + // test de connexion + $dbConnection = new mysqli( + $_POST["options"]["host"], $_POST["options"]["user"], + $_POST["options"]["password"], $_POST["options"]["dbname"]); + if ($dbConnection->connect_error) { + $errors["host"] = "Connexion impossible à la base de données."; + } + } + } + + if (empty($errors)) { + if ($_POST["type"] == "db") { + $config->set("storage", "type", "db"); + $config->set("storage", "options", array( + "host" => $_POST["options"]["host"], + "user" => $_POST["options"]["user"], + "password" => $_POST["options"]["password"], + "dbname" => $_POST["options"]["dbname"], + )); + } else { + $config->set("storage", "type", "files"); + } + $config->save(); + + if ($_POST["type"] == "db" && !empty($_POST["importtodb"])) { + // installation de la base + require DOCUMENT_ROOT."/others/install/schema.php"; + + $userStorageDb = new \App\Storage\Db\User($dbConnection); + + $users = array(); + $usersDb = $userStorageDb->fetchAll(); // utilisateurs actuellement en BDD + foreach ($usersDb AS $user) { + $users[$user->getUsername()] = $user; + } + unset($usersDb); + + + $userStorageFiles = new \App\Storage\File\User(DOCUMENT_ROOT."/var/users.db"); + $usersFiles = $userStorageFiles->fetchAll(); + foreach ($usersFiles AS $user) { + if (!isset($users[$user->getUsername()])) { + $userStorageDb->save($user); + } + } + + $users = $userStorageDb->fetchAll(); + foreach ($users AS $user) { + $file = DOCUMENT_ROOT."/var/configs/".$user->getUsername().".csv"; + if (!is_file($file)) { + continue; + } + $storageFiles = new \App\Storage\File\Alert($file); + $storageDb = new \App\Storage\Db\Alert($userStorageDb->getDbConnection(), $user); + $alerts = $storageFiles->fetchAll(); + foreach ($alerts AS $alert) { + $storageDb->save($alert, $forceinsert=true); + } + } + } + + header("LOCATION: ?mod=admin&a=storage&success=1"); + exit; + } +} \ No newline at end of file diff --git a/sources/app/admin/scripts/upgrade.php b/sources/app/admin/scripts/upgrade.php new file mode 100644 index 0000000..ba5357c --- /dev/null +++ b/sources/app/admin/scripts/upgrade.php @@ -0,0 +1,76 @@ +get("general", "url_version", "")) { + $updater->setUrlVersion($url); +} +if ($url = $config->get("general", "url_archive", "")) { + $updater->setUrlArchive($url); +} + +if (isset($_POST["checkVersion"])) { + unset($_SESSION["lbcLastVersion"], $_SESSION["lbcLastVersionTime"]); + header("LOCATION: ?mod=admin&a=upgrade"); + exit; +} +if ($_SERVER["REQUEST_METHOD"] == "POST" || empty($_SESSION["lbcLastVersion"]) || empty($_SESSION["lbcLastVersionTime"])) { + try { + $_SESSION["lbcLastVersion"] = $updater->getLastVersion(); + $_SESSION["lbcLastVersionTime"] = time(); + } catch (Exception $e) { + + } +} +$lastVersion = ""; +if (!empty($_SESSION["lbcLastVersion"])) { + $lastVersion = $_SESSION["lbcLastVersion"]; +} + +$errors = array(); + +$allow_update = true; +if (!is_writable(DOCUMENT_ROOT."/version.php")) { + $allow_update = false; + $errors[] = "Le fichier version.php est en lecture seule, la mise à jour automatique ne peut être effectuée.". + "
Vérifiez que tous les fichiers soient accéssibles en écriture (pas seulement le fichier version.php)."; +} elseif (is_file(DOCUMENT_ROOT."/var/.lock")) { + $allow_update = false; + $errors[] = "Une vérification de nouvelle annonce est en cours, veuillez attendre la fin de celle-ci pour mettre à jour."; +} + +$currentVersion = $config->get("general", "version"); +$upgradeStarted = version_compare($currentVersion, APPLICATION_VERSION, "<"); +$upgradeAvailable = version_compare($currentVersion, $lastVersion, "<"); + +if ($upgradeStarted && !empty($_POST["upgrade"])) { + $updater->update($currentVersion, $lastVersion); + // mise à jour du numéro de version dans la config. + $config->set("general", "version", $lastVersion); + $config->save(); + header("LOCATION: ?mod=admin&a=upgrade"); + exit; + +} elseif ($allow_update && $upgradeAvailable && !empty($_POST["upgrade"])) { + file_put_contents(DOCUMENT_ROOT."/var/.lock_update", time()); + try { + $updater->installFiles($lastVersion); + } catch (Exception $e) { + $errors[] = $e->getMessage(); + } + $version = require DOCUMENT_ROOT."/version.php"; + if ($version != $lastVersion) { + $errors[] = "La mise à jour semble avoir échouée."; + } else { + $updater->update($currentVersion, $lastVersion); + + // mise à jour du numéro de version dans la config. + $config->set("general", "version", $lastVersion); + $config->save(); + } + + unlink(DOCUMENT_ROOT."/var/.lock_update"); + if (!$errors) { + header("LOCATION: ?mod=admin&a=upgrade"); + exit; + } +} \ No newline at end of file diff --git a/sources/app/admin/scripts/users.php b/sources/app/admin/scripts/users.php new file mode 100644 index 0000000..ce9d915 --- /dev/null +++ b/sources/app/admin/scripts/users.php @@ -0,0 +1,2 @@ +fetchAll(); diff --git a/sources/app/admin/views/add-user.phtml b/sources/app/admin/views/add-user.phtml new file mode 100644 index 0000000..2fa4406 --- /dev/null +++ b/sources/app/admin/views/add-user.phtml @@ -0,0 +1,30 @@ +
+

Ajouter un utilisateur

+
+
+
+ + +

+ +
+
+
+ + +

+ +
+
+
+ + +

+ +
+
+

+ | annuler

+
diff --git a/sources/app/admin/views/delete-user.phtml b/sources/app/admin/views/delete-user.phtml new file mode 100644 index 0000000..b3c9a30 --- /dev/null +++ b/sources/app/admin/views/delete-user.phtml @@ -0,0 +1,6 @@ +
+

+ Supprimer l'utilisateur "getUsername()); ?>" ?
+ | non +

+
\ No newline at end of file diff --git a/sources/app/admin/views/layout.phtml b/sources/app/admin/views/layout.phtml new file mode 100644 index 0000000..45e39cd --- /dev/null +++ b/sources/app/admin/views/layout.phtml @@ -0,0 +1,38 @@ + + + + Alerte mail pour Leboncoin.fr + + + + + + + +
+

Système d'alerte Leboncoin

+ +
+ +
+ +
+ + + + + \ No newline at end of file diff --git a/sources/app/admin/views/log.phtml b/sources/app/admin/views/log.phtml new file mode 100644 index 0000000..7dc8520 --- /dev/null +++ b/sources/app/admin/views/log.phtml @@ -0,0 +1,19 @@ +

Fichier jounal

+

Voici les dernières lignes du fichier log:

+
    + +
  • Rien à signaler.
  • + + + > + + +
+ \ No newline at end of file diff --git a/sources/app/admin/views/mail.phtml b/sources/app/admin/views/mail.phtml new file mode 100644 index 0000000..6b6d4c4 --- /dev/null +++ b/sources/app/admin/views/mail.phtml @@ -0,0 +1,84 @@ + +
+

Test d'envoi de mail

+ +

Vous devriez recevoir un mail de test à l'adresse suivante :

+ +

Erreur de l'envoi du mail :

+ +
+ +
+

Configuration de l'envoi des mails

+
+
+
+ " size="40" /> +
+
+
+  
+ " /> +
+
+  
+ " size="10" /> + +

+ +
+ +
+ +
+
+  
+ " /> +
+
+  
+ " /> + +

+ +
+
+
+ +
+
+ +
+
+ " size="40" /> + +

+ +

Un mail de test sera envoyé à cette adresse

+
+
+

+ + +

+
diff --git a/sources/app/admin/views/proxy.phtml b/sources/app/admin/views/proxy.phtml new file mode 100644 index 0000000..71258bf --- /dev/null +++ b/sources/app/admin/views/proxy.phtml @@ -0,0 +1,62 @@ + +
+

Test de connexion

+
    + hasProxy()) : ?> +
  • aucun proxy configuré.
  • + +
  • utilisation d'un proxy pour la connexion.
  • + +
  • Connexion à portail.free.fr : + + échec + (erreur: ) + + réussi + +
  • +
  • Connexion à Leboncoin : + + échec + (erreur: ) + + réussi + +
  • +
+
+ +

Configuration d'un serveur proxy

+

+ Vous pouvez configurer ici un proxy de connexion qui sera + utilisé pour se connecter à Leboncoin. +

+
+

Paramètres du proxy

+
+
+
" />
+
+
" size="7" />
+
+ +

Le serveur proxy requiert une identification ?

+
+
+
" /> +
+
+
/>
+
+

+ + +

+
diff --git a/sources/app/admin/views/storage.phtml b/sources/app/admin/views/storage.phtml new file mode 100644 index 0000000..5634d5f --- /dev/null +++ b/sources/app/admin/views/storage.phtml @@ -0,0 +1,102 @@ + + + +

Mise à jour de la configuration effectuée.

+ + +

Vous pouvez choisir de stocker les données de l'application + (utilisateurs, alertes, options, etc.) + dans des fichiers ou en base de données (MySQL).

+
+

Configuration du stockage des données

+ +
+
+
+ + + +

+ +
+
+
+ " /> + +

+ +
+
+
+ " /> + +

+ +
+
+
+ " /> + +

+ +
+
+
+ " /> + +

+ +
+ +
+ + +

+ +
+ +
+

+
+ + + + + + + diff --git a/sources/app/admin/views/upgrade.phtml b/sources/app/admin/views/upgrade.phtml new file mode 100644 index 0000000..3cd4d14 --- /dev/null +++ b/sources/app/admin/views/upgrade.phtml @@ -0,0 +1,54 @@ + +

+ ", $errors); ?> +

+
+ + + +

+ ATTENTION : + avant de lancer la mise à jour automatique, + merci d'effectuer une sauvegarde de vos données actuelles. +

+ + + + +
+

+ Cliquer sur "Mettre à jour" afin de terminer la mise à jour. +

+

+ + +

+
+ + +

Une mise à jour est disponible

+

+ Vous utilisez actuellement la version . + La version est disponible. +

+ +
+

+ + +

+
+ + + +

Vous utilisez déjà la dernière version.

+
+

+ Dernière vérification effectuée le +

+

+ + +

+
+ \ No newline at end of file diff --git a/sources/app/admin/views/users.phtml b/sources/app/admin/views/users.phtml new file mode 100644 index 0000000..9f321c7 --- /dev/null +++ b/sources/app/admin/views/users.phtml @@ -0,0 +1,23 @@ +

Nouvel utilisateur

+ + + + + + + + + + + + + + + +
Nom d'utilisateur 
getUsername()); ?> + getUsername() != "admin") : ?> + supprimer + + - + +
\ No newline at end of file diff --git a/sources/app/default/scripts/login.php b/sources/app/default/scripts/login.php new file mode 100644 index 0000000..93be68d --- /dev/null +++ b/sources/app/default/scripts/login.php @@ -0,0 +1,25 @@ +setUsername($username) + ->setPassword(sha1($password)); + if ($auth->authenticate()) { + if ($module == "default" && $action == "login") { + $redirect = "./"; + } else { + $redirect = $_SERVER["REQUEST_URI"]; + } + header("LOCATION: ".$redirect); + exit; + } + $errors["password"] = "Nom d'utilisateur ou mot de passe incorrecte."; + } +} \ No newline at end of file diff --git a/sources/app/default/scripts/logout.php b/sources/app/default/scripts/logout.php new file mode 100644 index 0000000..e9afd6f --- /dev/null +++ b/sources/app/default/scripts/logout.php @@ -0,0 +1,5 @@ +clear(); +header("LOCATION: ./"); +exit; \ No newline at end of file diff --git a/sources/app/default/views/index.phtml b/sources/app/default/views/index.phtml new file mode 100644 index 0000000..cea7b0f --- /dev/null +++ b/sources/app/default/views/index.phtml @@ -0,0 +1,11 @@ +

+ Utilisez le menu ci-dessus pour accéder à la gestion de vos alertes mails + et des flux RSS. +

+

+ Vous pouvez aussi : +

+ \ No newline at end of file diff --git a/sources/app/default/views/layout.phtml b/sources/app/default/views/layout.phtml new file mode 100644 index 0000000..9b55c1f --- /dev/null +++ b/sources/app/default/views/layout.phtml @@ -0,0 +1,38 @@ + + + + Alerte mail pour Leboncoin.fr + + + + + + + +
+

Système d'alerte Leboncoin

+ +
+ +
+ +
+ + + + + + \ No newline at end of file diff --git a/sources/app/default/views/login.phtml b/sources/app/default/views/login.phtml new file mode 100644 index 0000000..f461208 --- /dev/null +++ b/sources/app/default/views/login.phtml @@ -0,0 +1,26 @@ +
+

Identifiez-vous

+
+
+ +
+
+ + +

+ +
+
+ +
+
+ + +

+ +
+
+

+
\ No newline at end of file diff --git a/sources/app/default/views/upgrade.phtml b/sources/app/default/views/upgrade.phtml new file mode 100644 index 0000000..0a8745b --- /dev/null +++ b/sources/app/default/views/upgrade.phtml @@ -0,0 +1,14 @@ + + + + Mise à jour en cours. + + + + isAdmin()) : ?> +

Cliquez-ici afin d'achever la mise à jour.

+ +

Mise à jour en cours, veuillez patienter quelques instants …

+ + + \ No newline at end of file diff --git a/sources/app/install/init.php b/sources/app/install/init.php new file mode 100644 index 0000000..28b1ea0 --- /dev/null +++ b/sources/app/install/init.php @@ -0,0 +1,11 @@ +getFilename()) && !isset($_GET["success"])) { + header("LOCATION: ?mod=install&a=upgrade"); + exit; +} + +if ($action == "upgrade" && $auth->getUsername() != "admin") { + header("HTTP/1.1 403 Forbidden"); + exit; +} \ No newline at end of file diff --git a/sources/app/install/scripts/index.php b/sources/app/install/scripts/index.php new file mode 100644 index 0000000..0cd5e19 --- /dev/null +++ b/sources/app/install/scripts/index.php @@ -0,0 +1,83 @@ +connect_error) { + $formErrors["db"]["host"] = "Connexion impossible à la base de données."; + } + } + } + + if (!$formErrors) { + if (!is_dir(DOCUMENT_ROOT."/var/configs")) { + mkdir(DOCUMENT_ROOT."/var/configs"); + } + if (!is_dir(DOCUMENT_ROOT."/var/feeds")) { + mkdir(DOCUMENT_ROOT."/var/feeds"); + } + if (!is_dir(DOCUMENT_ROOT."/var/log")) { + mkdir(DOCUMENT_ROOT."/var/log"); + } + $config->set("general", "version", APPLICATION_VERSION); + if (isset($dbConnection)) { + $config->set("storage", "type", "db"); + $config->set("storage", "options", array( + "host" => $_POST["db"]["host"], + "user" => $_POST["db"]["user"], + "password" => $_POST["db"]["password"], + "dbname" => $_POST["db"]["dbname"], + )); + } else { + $config->set("storage", "type", "files"); + } + $config->save(); + + $storageType = $config->get("storage", "type", "files"); + if ($storageType == "db") { + // installation de la base + require DOCUMENT_ROOT."/others/install/schema.php"; + + $userStorage = new \App\Storage\Db\User($dbConnection); + } else { + $userStorage = new \App\Storage\File\User(DOCUMENT_ROOT."/var/users.db"); + } + + // table utilisateurs + $user = new \App\User\User(array( + "username" => "admin", + "password" => sha1($_POST["password"]) + )); + $userStorage->save($user); + + header("LOCATION: ?mod=install&success=true"); + exit; + } +} diff --git a/sources/app/install/scripts/upgrade.php b/sources/app/install/scripts/upgrade.php new file mode 100644 index 0000000..ba3530e --- /dev/null +++ b/sources/app/install/scripts/upgrade.php @@ -0,0 +1,17 @@ +getFilename())) { + header("LOCATION: ?mod=install"); + exit; +} + +if ($auth->getUsername() != "admin") { + header("HTTP/1.1 403 Forbidden"); + exit; +} + +$require_upgrade = false; + +if (-1 == version_compare($config->get("general", "version"), APPLICATION_VERSION)) { + $require_upgrade = true; +} \ No newline at end of file diff --git a/sources/app/install/views/index.phtml b/sources/app/install/views/index.phtml new file mode 100644 index 0000000..0fdd2d3 --- /dev/null +++ b/sources/app/install/views/index.phtml @@ -0,0 +1,116 @@ + +
  • ", $errors); ?>
+ +
+
+ Nouvelle installation + +
+
Mot de passe admin :
+
+ + +

+ +
+
Confirmer le mot de passe :
+
+ + +

+ +
+
+

Stockage des données

+

+ Par défaut, les données (utilisateurs, alertes, etc.) + sont stockées dans des fichiers. C'est l'installation la plus simple + et rapide ne nécessitant que peu de connaissance.

+

+ Toutefois, si vous le souhaitez vous pouvez enregistrer les données + dans une base de données MySQL (plus adapté et plus fiable). +

+
+
Type de stockage :
+
+ + + +

+ +
+
Hôte :
+
+ " /> + +

+ +
+
Utilisateur :
+
+ " /> + +

+ +
+
Mot de passe :
+
+ " /> + +

+ +
+
Nom de la base de données :
+
+ " /> + +

+ +
+
Cliquez sur le bouton suivant pour lancer l'installation.
+
+
+ +

Installation terminée.

+

Vous pouvez vous connecter avec les identifiants suivants :
+ Utilisateur: admin
+ Mot de passe: spécifié lors de l'installation.

+

continuer >

+ +
+
+ + \ No newline at end of file diff --git a/sources/app/install/views/upgrade.phtml b/sources/app/install/views/upgrade.phtml new file mode 100644 index 0000000..8441209 --- /dev/null +++ b/sources/app/install/views/upgrade.phtml @@ -0,0 +1,5 @@ + +

Mise à jour de l'application

+ +

Application à jour

+ \ No newline at end of file diff --git a/sources/app/mail/init.php b/sources/app/mail/init.php new file mode 100644 index 0000000..df2123d --- /dev/null +++ b/sources/app/mail/init.php @@ -0,0 +1,9 @@ +get("storage", "type", "files"); + if ($storageType == "db") { + $storage = new \App\Storage\Db\Alert($dbConnection, $userAuthed); + } else { + $storage = new \App\Storage\File\Alert(DOCUMENT_ROOT."/var/configs/".$auth->getUsername().".csv"); + } +} \ No newline at end of file diff --git a/sources/app/mail/scripts/check.php b/sources/app/mail/scripts/check.php new file mode 100644 index 0000000..9b9c791 --- /dev/null +++ b/sources/app/mail/scripts/check.php @@ -0,0 +1,561 @@ +_config = $config; + + if (function_exists("pcntl_signal")) { + pcntl_signal(SIGTERM, array($this, "sigHandler")); + pcntl_signal(SIGINT, array($this, "sigHandler")); + } + + $this->_httpClient = $client; + $this->_userStorage = $userStorage; + $this->_logger = Logger::getLogger("main"); + $this->_lockFile = DOCUMENT_ROOT."/var/.lock"; + + $this->_lock(); + $this->_running = true; + + $this->_logger->info("[Pid ".getmypid()."] Vérification des alertes."); + + $this->_mailer = new PHPMailer($exceptions=true); + $this->_mailer->setLanguage("fr", DOCUMENT_ROOT."/lib/PHPMailer/language/"); + $this->_mailer->CharSet = "utf-8"; + if ($config->hasSection("mailer")) { + if ($smtp = $config->get("mailer", "smtp", array())) { + $this->_mailer->SMTPKeepAlive = true; + if (!empty($smtp["host"])) { + $this->_mailer->Host = $smtp["host"]; + if (!empty($smtp["port"])) { + $this->_mailer->Port = $smtp["port"]; + } + $this->_mailer->isSMTP(); + } + if (!empty($smtp["username"])) { + $this->_mailer->SMTPAuth = true; + $this->_mailer->Username = $smtp["username"]; + } + if (!empty($smtp["password"])) { + $this->_mailer->SMTPAuth = true; + $this->_mailer->Password = $smtp["password"]; + } + if (!empty($smtp["secure"])) { + $this->_mailer->SMTPSecure = $smtp["secure"]; + } + } + if ($from = $config->get("mailer", "from", null)) { + $this->_mailer->Sender = $from; + $this->_mailer->From = $from; + $this->_mailer->FromName = $from; + } + } + $this->_mailer->isHTML(true); + } + + public function __destruct() + { + $this->shutdown(); + } + + public function check() + { + $checkStart = (int)$this->_config->get("general", "check_start", 7); + $checkEnd = (int)$this->_config->get("general", "check_end", 24); + if ($checkStart > 23) { + $checkStart = 0; + } + if ($checkEnd < 1) { + $checkEnd = 24; + } + $hour = (int)date("G"); + if ($hour < $checkStart || $hour >= $checkEnd) { + $this->_logger->info("[Pid ".getmypid()."] Hors de la plage horaire. Contrôle annulé."); + return; + } + $this->_checkConnection(); + $users = $this->_userStorage->fetchAll(); + + // génération d'URL court pour les SMS + $curlTinyurl = curl_init(); + curl_setopt($curlTinyurl, CURLOPT_RETURNTRANSFER, 1); + + $storageType = $this->_config->get("storage", "type", "files"); + + $baseurl = $this->_config->get("general", "baseurl", ""); + + foreach ($users AS $user) { + if ($storageType == "db") { + $storage = new \App\Storage\Db\Alert($this->_userStorage->getDbConnection(), $user); + $this->_logger->info("[Pid ".getmypid()."] USER : ".$user->getUsername()); + } else { + $file = DOCUMENT_ROOT."/var/configs/".$user->getUsername().".csv"; + if (!is_file($file)) { + continue; + } + $storage = new \App\Storage\File\Alert($file); + $this->_logger->info("[Pid ".getmypid()."] USER : ".$user->getUsername()." (".$file.")"); + } + + $reset_filename = DOCUMENT_ROOT."/var/tmp/reset_".$user->getId(); + $reset = is_file($reset_filename); + if ($reset) { + unlink($reset_filename); + } + + // configuration des notifications. + $notifications = array(); + $notifications_params = $user->getOption("notification"); + if ($notifications_params && is_array($notifications_params)) { + foreach ($notifications_params AS $notification_name => $options) { + if (!is_array($options)) { + continue; + } + try { + $notifications[$notification_name] = \Message\AdapterFactory::factory($notification_name, $options); + $this->_logger->debug( + "[Pid ".getmypid()."] USER : ".$user->getUsername(). + " -> Notification ".get_class($notifications[$notification_name])." activée" + ); + } catch (\Exception $e) { + $this->_logger->warn( + "[Pid ".getmypid()."] USER : ".$user->getUsername(). + " -> Notification ".$notification_name." invalide" + ); + } + } + } + + $alerts = $storage->fetchAll(); + $this->_logger->info( + "[Pid ".getmypid()."] USER : ".$user->getUsername()." -> ". + count($alerts)." alerte".(count($alerts) > 1 ? "s" : ""). + " trouvée".(count($alerts) > 1 ? "s" : "")); + if (count($alerts) == 0) { + continue; + } + foreach ($alerts AS $i => $alert) { + $log_id = "[Pid ".getmypid()."] USER : ".$user->getUsername()." - ALERT ID : ".$alert->id." -> "; + + try { + $config = SiteConfigFactory::factory($alert->url); + } catch (Exception $e) { + $this->_logger->warn($log_id.$e->getMessage()); + continue; + } + + $unique_ads = $user->getOption("unique_ads"); + + /** + * Si le site ne fourni pas de date par annonce, + * on est obligé de baser la dernière annonce reçue sur l'ID. + */ + if (!$config->getOption("has_date")) { + $unique_ads = true; + } + + $currentTime = time(); + if (!isset($alert->time_updated)) { + $alert->time_updated = 0; + } + if ($reset) { + $alert->time_updated = 0; + $alert->last_id = array(); + $alert->max_id = 0; + $alert->time_last_ad = 0; + } + if (((int)$alert->time_updated + (int)$alert->interval*60) > $currentTime + || $alert->suspend) { + continue; + } + $this->_logger->info($log_id."URL : ".$alert->url); + try { + $parser = \AdService\ParserFactory::factory($alert->url); + } catch (\AdService\Exception $e) { + $this->_logger->err($log_id." ".$e->getMessage()); + continue; + } + + $this->_logger->debug($log_id."Dernière mise à jour : ".( + !empty($alert->time_updated) ? + date("d/m/Y H:i", (int) $alert->time_updated) : + "inconnue" + )); + $this->_logger->debug($log_id."Dernière annonce : ".( + !empty($alert->time_last_ad) ? + date("d/m/Y H:i", (int) $alert->time_last_ad) : + "inconnue" + )); + $alert->time_updated = $currentTime; + if (!$content = $this->_httpClient->request($alert->url)) { + $this->_logger->error($log_id."Curl Error : ".$this->_httpClient->getError()); + continue; + } + $cities = array(); + if ($alert->cities) { + $cities = array_map("trim", explode("\n", mb_strtolower($alert->cities))); + } + $filter = new \AdService\Filter(array( + "price_min" => $alert->price_min, + "price_max" => $alert->price_max, + "cities" => $cities, + "price_strict" => (bool)$alert->price_strict, + "categories" => $alert->getCategories(), + "min_id" => $unique_ads ? $alert->max_id : 0, + "exclude_ids" => $alert->last_id, + )); + $ads = $parser->process( + $content, + $filter, + parse_url($alert->url, PHP_URL_SCHEME) + ); + $countAds = count($ads); + + /** + * Migrer vers le nouveau système de détection d'annonce. + */ + if (is_numeric($alert->last_id)) { + $filter->setExcludeIds(array()); + $alert->last_id = array(); + $tmp_ads = $parser->process( + $content, + $filter, + parse_url($alert->url, PHP_URL_SCHEME) + ); + foreach ($tmp_ads AS $tmp_ad) { + $alert->last_id[] = $tmp_ad->getId(); + } + unset($tmp_ads, $tmp_ad); + } + + if ($countAds == 0) { + $storage->save($alert); + continue; + } + $siteConfig = \AdService\SiteConfigFactory::factory($alert->url); + $newAds = array(); + foreach ($ads AS $ad) { + $time = $ad->getDate(); + $id = $ad->getId(); + $newAds[$id] = require DOCUMENT_ROOT."/app/mail/views/mail-ad.phtml"; + if (!in_array($id, $alert->last_id)) { + array_unshift($alert->last_id, $id); + } + if ($time && $alert->time_last_ad < $time) { + $alert->time_last_ad = $time; + } + if ($unique_ads && $id > $alert->max_id) { + $alert->max_id = $id; + } + } + + // On conserve 250 IDs d'annonce vues. + if (250 < count($alert->last_id)) { + $alert->last_id = array_slice($alert->last_id, 0, 250); + } + + if (!$newAds) { + $storage->save($alert); + continue; + } + $countAds = count($newAds); + $this->_logger->info( + $log_id.$countAds. + " annonce".($countAds > 1 ? "s" : ""). + " trouvée".($countAds > 1?"s":"") + ); + $this->_mailer->clearAddresses(); + $error = false; + if ($alert->send_mail) { + try { + $emails = explode(",", $alert->email); + foreach ($emails AS $email) { + $this->_mailer->addAddress(trim($email)); + } + } catch (phpmailerException $e) { + $this->_logger->warn($log_id.$e->getMessage()); + $error = true; + } + if (!$error) { + if ($alert->group_ads) { + $newAdsCount = count($newAds); + $subject = "Alerte ".$siteConfig->getOption("site_name")." : ".$alert->title; + $message = ' +

Alerte : '.htmlspecialchars($alert->title, null, "UTF-8").'

+

LIEN DE RECHERCHE'; + if ($baseurl) { + $message .= ' + - MODIFIER + - ACTIVER / DÉSACTIVER + - SUPPRIMER + '; + } + $message .= '

'; + $message .= '
'; + $message .= '

'. + $newAdsCount.' nouvelle'.($newAdsCount > 1?'s':''). + ' annonce'.($newAdsCount > 1?'s':''). + ' - '.date("d/m/Y à H:i", $currentTime).'

'; + $message .= '

'; + $message .= implode("


", $newAds); + $message .= '

'; + + $this->_mailer->Subject = $subject; + $this->_mailer->Body = $message; + try { + $this->_mailer->send(); + } catch (phpmailerException $e) { + $this->_logger->warn($log_id.$e->getMessage()); + } + } else { + $newAds = array_reverse($newAds, true); + foreach ($newAds AS $id => $ad) { + $subject = ($alert->title?$alert->title." : ":"").$ads[$id]->getTitle(); + $message = ' +

Alerte : '.htmlspecialchars($alert->title, null, "UTF-8").'

+

LIEN DE RECHERCHE'; + if ($baseurl) { + $message .= ' + - MODIFIER + - ACTIVER / DÉSACTIVER + - SUPPRIMER + '; + } + $message .= '

'; + $message .= '

'; + $message .= $ad; + + $this->_mailer->Subject = $subject; + $this->_mailer->Body = $message; + try { + $this->_mailer->send(); + } catch (phpmailerException $e) { + $this->_logger->warn($log_id.$e->getMessage()); + } + } + } + } + } + if ($notifications && ( + $alert->send_sms_free_mobile + || $alert->send_sms_ovh + || $alert->send_pushbullet + || $alert->send_notifymyandroid + || $alert->send_pushover + )) { + if ($countAds < 5) { // limite à 5 SMS + foreach ($newAds AS $id => $ad) { + $ad = $ads[$id]; // récupère l'objet. + $url = $ad->getLink(); + if (false !== strpos($url, "leboncoin")) { + $url = "https://mobile.leboncoin.fr/vi/".$ad->getId().".htm"; + } + curl_setopt($curlTinyurl, CURLOPT_URL, "http://tinyurl.com/api-create.php?url=".$url); + if ($url = curl_exec($curlTinyurl)) { + $msg = "Nouvelle annonce ".($alert->title?$alert->title." : ":"").$ad->getTitle(); + $others = array(); + if ($ad->getPrice()) { + $others[] = number_format($ad->getPrice(), 0, ',', ' ').$ad->getCurrency(); + } + if ($ad->getCity()) { + $others[] = $ad->getCity(); + } elseif ($ad->getCountry()) { + $others[] = $ad->getCountry(); + } + if ($others) { + $msg .= " (".implode(", ", $others).")"; + } + $params = array( + "title" => "Alerte ".$siteConfig->getOption("site_name"), + "description" => "Nouvelle annonce".($alert->title ? " pour : ".$alert->title : ""), + "url" => $url, + ); + foreach ($notifications AS $key => $notifier) { + switch ($key) { + case "freeMobile": + $key_test = "send_sms_free_mobile"; + break; + case "ovh": + $key_test = "send_sms_ovh"; + break; + default: + $key_test = "send_".$key; + } + if (isset($alert->$key_test) && $alert->$key_test) { + try { + $notifier->send($msg, $params); + } catch (Exception $e) { + $this->_logger->warn( + $log_id."Erreur sur envoi via ". + get_class($notifier). + ": (".$e->getCode().") ". + $e->getMessage() + ); + } + } + } + } + } + } else { // envoi un msg global + curl_setopt($curlTinyurl, CURLOPT_URL, "http://tinyurl.com/api-create.php?url=".$alert->url); + if ($url = curl_exec($curlTinyurl)) { + $msg = "Il y a ".$countAds." nouvelles annonces pour votre alerte '".($alert->title?$alert->title:"sans nom")."'"; + $params = array( + "title" => "Alerte ".$siteConfig->getOption("site_name"), + "description" => "Nouvelle".($countAds > 1 ? "s" : ""). + " annonce".($countAds > 1 ? "s" : ""). + ($alert->title ? " pour : ".$alert->title : ""), + "url" => $url, + ); + foreach ($notifications AS $key => $notifier) { + switch ($key) { + case "freeMobile": + $key_test = "send_sms_free_mobile"; + break; + case "ovh": + $key_test = "send_sms_ovh"; + break; + default: + $key_test = "send_".$key; + } + if (isset($alert->$key_test) && $alert->$key_test) { + try { + $notifier->send($msg, $params); + } catch (Exception $e) { + $this->_logger->warn( + $log_id."Erreur sur envoi via ". + get_class($notifier). + ": (".$e->getCode().") ". + $e->getMessage() + ); + } + } + } + } + } + } + $storage->save($alert); + } + } + + curl_close($curlTinyurl); + $this->_mailer->smtpClose(); + } + + public function shutdown() + { + if ($this->_running && is_file($this->_lockFile)) { + unlink($this->_lockFile); + } + } + + public function sigHandler($no) + { + if (in_array($no, array(SIGTERM, SIGINT))) { + $this->_logger->info("[Pid ".getmypid()."] QUIT (".$no.")"); + $this->shutdown(); + exit; + } + } + + protected function _checkConnection() + { + // teste la connexion + $this->_httpClient->setDownloadBody(false); + if (false === $this->_httpClient->request("https://www.leboncoin.fr")) { + throw new Exception("Connexion vers https://www.leboncoin.fr échouée". + (($error = $this->_httpClient->getError())?" (erreur: ".$error.")":"")."."); + } + if (200 != $code = $this->_httpClient->getRespondCode()) { + throw new Exception("Code HTTP différent de 200 : ".$code); + } + $this->_httpClient->setDownloadBody(true); + } + + protected function _lock() + { + if (is_file($this->_lockFile)) { + throw new Exception("Un processus est en cours d'exécution."); + } + file_put_contents($this->_lockFile, time()."\n".getmypid()); + return $this; + } +} + +require __DIR__."/../../../bootstrap.php"; + +// lib +require_once "PHPMailer/class.phpmailer.php"; + +// modèle +$storageType = $config->get("storage", "type", "files"); +if ($storageType == "db") { + $userStorage = new \App\Storage\Db\User($dbConnection); +} else { + $userStorage = new \App\Storage\File\User(DOCUMENT_ROOT."/var/users.db"); +} + +if (is_file(DOCUMENT_ROOT."/var/.lock_update")) { + Logger::getLogger("main")->info("Tâche annulée : une mise à jour de l'application est en cours."); + return; +} + +try { + $main = new Main($config, $client, $userStorage); +} catch (\Exception $e) { + Logger::getLogger("main")->info($e->getMessage()); + return; +} + +try { + $main->check(); +} catch (\Exception $e) { + Logger::getLogger("main")->warn($e->getMessage()); +} +$main->shutdown(); + + + + + diff --git a/sources/app/mail/scripts/form-delete.php b/sources/app/mail/scripts/form-delete.php new file mode 100644 index 0000000..c9c4fe7 --- /dev/null +++ b/sources/app/mail/scripts/form-delete.php @@ -0,0 +1,14 @@ +fetchById($_GET["id"]); +if (!$alert->id) { + header("LOCATION: ./?mod=mail"); exit; +} +if ($_SERVER["REQUEST_METHOD"] == "POST") { + if (isset($_POST["id"]) && $_POST["id"] == $_GET["id"]) { + $storage->delete($alert); + } + header("LOCATION: ./?mod=mail"); exit; +} \ No newline at end of file diff --git a/sources/app/mail/scripts/form.php b/sources/app/mail/scripts/form.php new file mode 100644 index 0000000..2e46b1c --- /dev/null +++ b/sources/app/mail/scripts/form.php @@ -0,0 +1,82 @@ +fetchById($_GET["id"]); +} +if (empty($alert)) { + $alert = new App\Mail\Alert(); +} + +$categoryCollection = new \Lbc\CategoryCollection(); + +if ($_SERVER["REQUEST_METHOD"] == "POST") { + foreach ($_POST AS $name => $value) { + if (is_array($value)) { + $_POST[$name] = array_map("trim", $_POST[$name]); + } else { + $_POST[$name] = trim($_POST[$name]); + } + } + $alert->fromArray($_POST); + if (empty($alert->send_mail) + && empty($alert->send_sms_free_mobile) + && empty($alert->send_sms_ovh) + && empty($alert->send_pushbullet) + && empty($alert->send_notifymyandroid) + && empty($alert->send_pushover) + ) { + $errors["send_type"] = "Vous devez sélectionner au moins un moyen de communication."; + } + if (empty($alert->email)) { + $errors["email"] = "Ce champ est obligatoire."; + } + if (empty($alert->title)) { + $errors["title"] = "Ce champ est obligatoire."; + } + if (empty($alert->price_min)) { + $alert->price_min = -1; + } + if (empty($alert->price_max)) { + $alert->price_max = -1; + } + if ($alert->price_min != (int)$alert->price_min) { + $errors["price"] = "Valeur de \"prix min\" non valide. "; + } + if ($alert->price_max != (int)$alert->price_max) { + $errors["price"] .= "Valeur de \"prix max\" non valide."; + } + if (!empty($_POST["price_strict"])) { + $alert->price_strict = (int)(bool)$_POST["price_strict"]; + } else { + $alert->price_strict = false; + } + $alert->group = !empty($_POST["group"])?trim($_POST["group"]):""; + if (empty($alert->url)) { + $errors["url"] = "Ce champ est obligatoire."; + } else { + try { + $siteConfig = \AdService\SiteConfigFactory::factory($alert->url); + if (false !== strpos($alert->url, "leboncoin.fr")) { + $alert->url = rtrim(preg_replace("#(o|sp)=[0-9]*&?#", "", $alert->url), "?&"); + } + } catch (\AdService\Exception $e) { + $errors["url"] = "Cette adresse ne semble pas valide."; + } + } + $alert->interval = (int)$alert->interval; + if ($alert->interval != (int)$alert->interval || $alert->interval < 0) { + $errors["interval"] = "Cette valeur n'est pas valide."; + } + if (empty($errors)) { + if (!empty($_POST["categories"])) { + if (is_array($alert->categories)) { + $alert->categories = implode(",", $_POST["categories"]); + } else { + $alert->categories = null; + } + } else { + $alert->categories = null; + } + $storage->save($alert); + header("LOCATION: ./?mod=mail"); exit; + } +} \ No newline at end of file diff --git a/sources/app/mail/scripts/index.php b/sources/app/mail/scripts/index.php new file mode 100644 index 0000000..65c461d --- /dev/null +++ b/sources/app/mail/scripts/index.php @@ -0,0 +1,86 @@ +fetchAll(); +$sort = ""; +$order = isset($_SESSION["mail"]["order"])?$_SESSION["mail"]["order"]:"asc"; + +if (isset($_SESSION["mail"]["sort"])) { + $sort = $_SESSION["mail"]["sort"]; + setlocale(LC_CTYPE, "fr_FR.UTF-8"); + usort($alerts, function ($alert1, $alert2) { + $key = $_SESSION["mail"]["sort"]; + $param1 = mb_strtolower($alert1->$key); + $param2 = mb_strtolower($alert2->$key); + if ($key == "title" && function_exists("iconv")) { + $param1 = iconv("UTF-8", "ASCII//TRANSLIT//IGNORE", $param1); + $param2 = iconv("UTF-8", "ASCII//TRANSLIT//IGNORE", $param2); + } + if ($param1 < $param2) { + return -1; + } + if ($param1 > $param2) { + return 1; + } + return 0; + }); +} +if (isset($_SESSION["mail"]["order"]) && $_SESSION["mail"]["order"] == "desc") { + $alerts = array_reverse($alerts); +} + +// configuration du tableau d'affichage +$showCities = false; +$showPrice = false; + +// trie les alertes par groupes +$alertsByGroup = array(); +$groups = array(); +foreach ($alerts AS $alert) { + $group = $alert->group?$alert->group:"Sans groupe"; + $groups[] = $group; + $alertsByGroup[$group][] = $alert; + if (-1 != $alert->price_min || -1 != $alert->price_max) { + $showPrice = true; + } + if ($alert->cities) { + $showCities = true; + } +} +$groups = array_unique($groups); +sort($groups); +if (in_array("Sans groupe", $groups)) { + // met les alertes sans groupe à la fin. + unset($groups[array_search("Sans groupe", $groups)]); + $groups[] = "Sans groupe"; +} + +$notification["freeMobile"] = $userAuthed->hasSMSFreeMobile(); +$notification["ovh"] = $userAuthed->hasSMSOvh(); +$notification["pushbullet"] = $userAuthed->hasPushbullet(); +$notification["notifymyandroid"] = $userAuthed->hasNotifyMyAndroid(); +$notification["pushover"] = $userAuthed->hasPushover(); + + + + + + + + + + + diff --git a/sources/app/mail/scripts/reset.php b/sources/app/mail/scripts/reset.php new file mode 100644 index 0000000..369dfc4 --- /dev/null +++ b/sources/app/mail/scripts/reset.php @@ -0,0 +1,8 @@ +getId(), time()); + + header("LOCATION: ./?mod=mail"); exit; +} \ No newline at end of file diff --git a/sources/app/mail/scripts/toggle_status.php b/sources/app/mail/scripts/toggle_status.php new file mode 100644 index 0000000..712f06c --- /dev/null +++ b/sources/app/mail/scripts/toggle_status.php @@ -0,0 +1,12 @@ +fetchById($_GET["id"])) { + $status = isset($_GET["s"])?$_GET["s"]:""; + if (in_array($status, array("suspend", "send_mail", "send_sms_free_mobile", + "send_sms_ovh", "send_pushbullet", "send_notifymyandroid", + "send_pushover"))) { + $alert->$status = !$alert->$status; + $storage->save($alert); + } +} +header("LOCATION: ./?mod=mail"); exit; \ No newline at end of file diff --git a/sources/app/mail/views/form-delete.phtml b/sources/app/mail/views/form-delete.phtml new file mode 100644 index 0000000..813c706 --- /dev/null +++ b/sources/app/mail/views/form-delete.phtml @@ -0,0 +1,14 @@ +
+
+ Supprimer cette alerte ? +
    +
  • Titre : title); ?>
  • +
  • Url : url); ?>
  • +
+

+ " /> + + | Non +

+
+
diff --git a/sources/app/mail/views/form.phtml b/sources/app/mail/views/form.phtml new file mode 100644 index 0000000..a8b6681 --- /dev/null +++ b/sources/app/mail/views/form.phtml @@ -0,0 +1,211 @@ +getCategories(); +?> +

Création d'une nouvelle alerte

+
+

Options obligatoires

+
+
+ +
+
+ " /> + +

+ +
+
+ +
+
+ + +

+ +
+ hasSMSFreeMobile() || $userAuthed->hasSMSOvh() || $userAuthed->hasPushbullet()) : ?> +
+ +
+
+ + + + + + + + hasSMSFreeMobile()) : ?> + + + hasSMSOvh()) : ?> + + + hasPushbullet()) : ?> + + + hasNotifyMyAndroid()) : ?> + + + hasPushover()) : ?> + + + +

+ +
+ +
+ +
+ +
+
+ +

Vous pouvez indiquer plusieurs adresses en les séparant par une virgule.

+ +

+ +
+
+ + group_ads || $alert->interval != 30 || $alert->group; ?> +

Plus d'options (cliquez pour )

+
;"> +
+ + +
+
+ +
+
+ + +

+ +
+
+
+ +

Les alertes seront affichées par groupe sur la page de vos alertes.

+
+
+ + price_min != -1 || $alert->price_max != -1 + || $alert->price_strict || !empty($alert->cities) || $alertCategories; ?> +

Filtres supplémentaires (cliquez pour )

+
;"> +
+ +
+
+ + + +

+ +
+
+ + +
+
+
+ +
+
+
+

Attention : + ce filtre ne doit être utilisé que pour les recherches sur "toutes les catégories".

+ + fetchAll() AS $group => $categories) : ?> +
+

+ $category) : ?> + + +
+ +
+
+

+ | annuler

+
+ + + + + + + + + + diff --git a/sources/app/mail/views/index.phtml b/sources/app/mail/views/index.phtml new file mode 100644 index 0000000..fc742f8 --- /dev/null +++ b/sources/app/mail/views/index.phtml @@ -0,0 +1,153 @@ +

+ + 1?"alertes enregistrées":"alerte enregistrée"; ?> | + + Ajouter une alerte | + Tout renvoyer +

+ + + + 1 || $group != "Sans groupe") : ?> +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Envoyer à TitreIntervallePrixVillesEnvoyer par emailSMS Free MobileSMS OVHPushbulletNotityMyAndroidPushoverActif 
", htmlspecialchars($alert->email)); ?>title?htmlspecialchars($alert->title):"-"; ?>interval; ?> mins + price_min != -1 && $alert->price_max != -1) : ?> + entre price_min; ?>€ et price_max; ?>€ + price_min != -1) : ?> + à partir de price_min; ?>€ + price_max != -1) : ?> + jusque price_max; ?>€ + + - + + + cities) : ?> +
  • +
  • ", htmlspecialchars($alert->cities)); ?> +
+ + - + +
+ .png" alt="" /> + + .png" alt="" /> + + .png" alt="" /> + + .png" alt="" /> + + .png" alt="" /> + + .png" alt="" /> + + .png" alt="" /> + + modifier | + supprimer +
+ + \ No newline at end of file diff --git a/sources/app/mail/views/mail-ad.phtml b/sources/app/mail/views/mail-ad.phtml new file mode 100644 index 0000000..74a288f --- /dev/null +++ b/sources/app/mail/views/mail-ad.phtml @@ -0,0 +1,46 @@ + +
+ getDate()) : ?> + Publié le getDate()); ?> +
+ + + Nom : + getTitle(); ?> + getLinkMobile()) : ?> + (Version Mobile) + + getPrice()) : ?> +     Prix : getPrice(), 0, ',', ' '); ?> getCurrency(); ?> + + + getCategory()) : ?> +
+ Catégorie : getCategory(); ?> + +
+ getCountry()) : ?> + Département : getCountry(); ?> +      + + getCity()) : ?> + Ville : getCity(); ?> + + getOption("pro_visible")) : ?> +
Annonce de getProfessional()?'professionnel':'particulier.'; ?> + +
+ getUrgent()) : ?> + urgent + + getThumbnailLink()) : ?> +
+ +
Pas de photo disponible. + +
+ +
+ Renvoyer toutes les alertes ? +

+ En confirmant, vous recevrez toutes les annonces pour chaque alerte + lors du prochain contrôle de nouvelle annonce. +

+

+ + | Annuler +

+
+ diff --git a/sources/app/models/Mail/Alert.php b/sources/app/models/Mail/Alert.php new file mode 100644 index 0000000..85d5bfb --- /dev/null +++ b/sources/app/models/Mail/Alert.php @@ -0,0 +1,88 @@ + $value) { + $this->$key = $value; + } + if (!is_numeric($this->group_ads)) { + $this->group_ads = 1; + } + + /** + * Depuis 3.1, last_id contient les derniers IDs de la liste d'annonce + * et max_id l'ID max trouvé. + */ + if ($this->last_id && is_numeric($this->last_id) && !$this->max_id) { + $this->max_id = $this->last_id; + } + } + + public function getCategories() + { + if ($this->categories && is_string($this->categories)) { + return explode(",", $this->categories); + } + if (is_array($this->categories)) { + return $this->categories; + } + return array(); + } + + public function toArray() + { + return array( + "email" => $this->email, + "id" => $this->id, + "title" => $this->title, + "url" => $this->url, + "interval" => $this->interval, + "time_last_ad" => $this->time_last_ad, + "time_updated" => $this->time_updated, + "price_min" => $this->price_min, + "price_max" => $this->price_max, + "price_strict" => $this->price_strict, + "cities" => $this->cities, + "suspend" => $this->suspend, + "group" => $this->group, + "group_ads" => $this->group_ads, + "categories" => $this->categories, + "send_mail" => $this->send_mail, + "send_sms_free_mobile" => $this->send_sms_free_mobile, + "last_id" => $this->last_id, + "max_id" => (int) $this->max_id, + "send_sms_ovh" => $this->send_sms_ovh, + "send_pushbullet" => $this->send_pushbullet, + "send_notifymyandroid" => $this->send_notifymyandroid, + "send_pushover" => $this->send_pushover + ); + } +} \ No newline at end of file diff --git a/sources/app/models/Storage/Alert.php b/sources/app/models/Storage/Alert.php new file mode 100644 index 0000000..e086d4b --- /dev/null +++ b/sources/app/models/Storage/Alert.php @@ -0,0 +1,14 @@ +_connection = $connection; + $this->_user = $user; + } + + public function fetchAll() + { + $alerts = array(); + $alertsDb = $this->_connection->query("SELECT * FROM ".$this->_table + ." WHERE user_id = ".$this->_user->getId()); + while ($alertDb = $alertsDb->fetch_assoc()) { + $alert = new \App\Mail\Alert(); + if (isset($alertDb["last_id"]) && !is_numeric($alertDb["last_id"])) { + $alertDb["last_id"] = json_decode($alertDb["last_id"], true); + if (!is_array($alertDb["last_id"])) { + $alertDb["last_id"] = array(); + } + } + $alert->fromArray($alertDb); + $alert->id = $alertDb["idstr"]; + $alerts[] = $alert; + } + return $alerts; + } + + public function fetchById($id) + { + $alert = null; + $alertDb = $this->_connection->query( + "SELECT * FROM ".$this->_table." WHERE user_id = ".$this->_user->getId()." + AND idstr = '".$this->_connection->real_escape_string($id)."'") + ->fetch_assoc(); + if ($alertDb) { + $alert = new \App\Mail\Alert(); + if (isset($alertDb["last_id"]) && !is_numeric($alertDb["last_id"])) { + $alertDb["last_id"] = json_decode($alertDb["last_id"], true); + if (!is_array($alertDb["last_id"])) { + $alertDb["last_id"] = array(); + } + } + $alert->fromArray($alertDb); + $alert->id = $alertDb["idstr"]; + } + return $alert; + } + + public function save(\App\Mail\Alert $alert, $forceInsert = false) + { + $options = $alert->toArray(); + + if (is_array($options["last_id"])) { + $options["last_id"] = json_encode($options["last_id"]); + } + + if (!$alert->id || $forceInsert) { + $options["user_id"] = $this->_user->getId(); + if (!$alert->id) { + $id = sha1(uniqid()); + $alert->id = $id; + } + $options["idstr"] = $alert->id; + unset($options["id"]); + $sqlOptions = array(); + foreach ($options AS $name => $value) { + if ($value === null) { + $value = "NULL"; + } elseif (is_bool($value)) { + $value = (int) $value; + } elseif (!is_numeric($value)) { + $value = "'".$this->_connection->real_escape_string($value)."'"; + } + $sqlOptions[$name] = $value; + } + $this->_connection->query("INSERT INTO ".$this->_table. + " (`".implode("`, `", array_keys($options)). + "`, `date_created`) VALUES (".implode(", ", $sqlOptions).", NOW())"); + } else { + $idStr = $options["id"]; + $sqlOptions = array(); + unset($options["id"]); + foreach ($options AS $name => $value) { + if ($value === null) { + $value = "NULL"; + } elseif (is_bool($value)) { + $value = (int) $value; + } elseif (!is_numeric($value)) { + $value = "'".$this->_connection->real_escape_string($value)."'"; + } + $sqlOptions[] = "`".$name."` = ".$value; + } + $this->_connection->query("UPDATE ".$this->_table." SET + ".implode(",", $sqlOptions). + " WHERE idstr = '".$this->_connection->real_escape_string($idStr)."'"); + } + return $this; + } + + public function delete(\App\Mail\Alert $alert) + { + $this->_connection->query("DELETE FROM ".$this->_table." + WHERE idstr = '".$this->_connection->real_escape_string($alert->id)."'"); + return $this; + } + + /** + * @param \mysqli $dbConnection + * @return \App\Storage\Db\User + */ + public function setDbConnection($dbConnection) + { + $this->_connection = $dbConnection; + return $this; + } + + /** + * @return \mysqli + */ + public function getDbConnection() + { + return $this->_connection; + } +} diff --git a/sources/app/models/Storage/Db/User.php b/sources/app/models/Storage/Db/User.php new file mode 100644 index 0000000..04de1f8 --- /dev/null +++ b/sources/app/models/Storage/Db/User.php @@ -0,0 +1,102 @@ +_connection = $connection; + } + + public function fetchAll() + { + $users = array(); + $usersDb = $this->_connection->query("SELECT * FROM ".$this->_table); + while ($userDb = $usersDb->fetch_object()) { + $user = new \App\User\User(); + $user->setId($userDb->id) + ->setPassword($userDb->password) + ->setUsername($userDb->username); + if (!empty($userDb->options)) { + $options = json_decode($userDb->options, true); + if (is_array($options)) { + $user->setOptions($options); + } + } + $users[] = $user; + } + return $users; + } + + public function fetchByUsername($username) + { + $user = null; + $userDb = $this->_connection->query( + "SELECT * FROM ".$this->_table." WHERE username = '". + $this->_connection->real_escape_string($username)."'") + ->fetch_object(); + if ($userDb) { + $user = new \App\User\User(); + $user->setId($userDb->id) + ->setPassword($userDb->password) + ->setUsername($userDb->username); + if (!empty($userDb->options)) { + $options = json_decode($userDb->options, true); + if (is_array($options)) { + $user->setOptions($options); + } + } + } + return $user; + } + + public function save(\App\User\User $user) + { + if (!$this->fetchByUsername($user->getUsername())) { + $this->_connection->query("INSERT INTO `".$this->_table. + "` (`username`, `password`, `options`) VALUES ( + '".$this->_connection->real_escape_string($user->getUsername())."', + '".$this->_connection->real_escape_string($user->getPassword())."', + '".$this->_connection->real_escape_string(json_encode($user->getOptions()))."' + )"); + } else { + $this->_connection->query("UPDATE `".$this->_table."` SET + `password` = '".$this->_connection->real_escape_string($user->getPassword())."', + `options` = '".$this->_connection->real_escape_string(json_encode($user->getOptions()))."' + WHERE id = ".$user->getId()); + } + return $this; + } + + public function delete(\App\User\User $user) + { + $this->_connection->query("DELETE FROM ".$this->_table." WHERE id = ".$user->getId()); + return $this; + } + + /** + * @param \mysqli $dbConnection + * @return \App\Storage\Db\User + */ + public function setDbConnection($dbConnection) + { + $this->_connection = $dbConnection; + return $this; + } + + /** + * @return \mysqli + */ + public function getDbConnection() + { + return $this->_connection; + } +} \ No newline at end of file diff --git a/sources/app/models/Storage/File/Alert.php b/sources/app/models/Storage/File/Alert.php new file mode 100644 index 0000000..0255655 --- /dev/null +++ b/sources/app/models/Storage/File/Alert.php @@ -0,0 +1,167 @@ +_filename = $filename; + $this->_checkFile(); + } + + public function fetchAll() + { + $alerts = array(); + if (is_file($this->_filename)) { + $fopen = fopen($this->_filename, "r"); + if ($header = fgetcsv($fopen, 0, ",", '"')) { + $nb_columns = count($header); + while (false !== $values = fgetcsv($fopen, 0, ",", '"')) { + $alert = new \App\Mail\Alert(); + $options = array_combine( + $header, + array_slice($values, 0, count($header)) + ); + if (isset($options["last_id"]) && !is_numeric($options["last_id"])) { + $options["last_id"] = json_decode($options["last_id"], true); + if (!is_array($options["last_id"])) { + $options["last_id"] = array(); + } + } + $alert->fromArray($options); + $alerts[$alert->id] = $alert; + } + } + fclose($fopen); + } + return $alerts; + } + + public function fetchById($id) + { + $alert = null; + if (is_file($this->_filename)) { + $fopen = fopen($this->_filename, "r"); + if ($header = fgetcsv($fopen, 0, ",", '"')) { + while (false !== $values = fgetcsv($fopen, 0, ",", '"')) { + $options = array_combine( + $header, + array_slice($values, 0, count($header)) + ); + if ($options["id"] == $id) { + if (isset($options["last_id"]) && !is_numeric($options["last_id"])) { + $options["last_id"] = json_decode($options["last_id"], true); + if (!is_array($options["last_id"])) { + $options["last_id"] = array(); + } + } + $alert = new \App\Mail\Alert(); + $alert->fromArray($options); + break; + } + } + } + fclose($fopen); + } + return $alert; + } + + public function save(\App\Mail\Alert $alert) + { + $alerts = $this->fetchAll(); + $fopen = fopen($this->_filename, "a"); + flock($fopen, LOCK_EX); + $fpNewFile = fopen($this->_filename.".new", "w"); + flock($fpNewFile, LOCK_EX); + + fputcsv($fpNewFile, $this->_header, ",", '"'); + $updated = false; + foreach ($alerts AS $a) { + if ($a->id == $alert->id) { + $a = $alert; + $updated = true; + } + $data = $a->toArray(); + if (is_array($data["last_id"])) { + $data["last_id"] = json_encode($data["last_id"]); + } + fputcsv($fpNewFile, $data, ",", '"'); + } + if (!$updated && !$alert->id) { + $alert->id = sha1(uniqid()); + fputcsv($fpNewFile, $alert->toArray(), ",", '"'); + } + + fclose($fpNewFile); + fclose($fopen); + file_put_contents($this->_filename, file_get_contents($this->_filename.".new")); + unlink($this->_filename.".new"); + return $this; + } + + public function delete(\App\Mail\Alert $alert) + { + $alerts = $this->fetchAll(); + $fopen = fopen($this->_filename, "a"); + flock($fopen, LOCK_EX); + $fpNewFile = fopen($this->_filename.".new", "w"); + flock($fpNewFile, LOCK_EX); + + fputcsv($fpNewFile, $this->_header, ",", '"'); + + unset($alerts[$alert->id]); + foreach ($alerts AS $a) { + fputcsv($fpNewFile, $a->toArray(), ",", '"'); + } + + fclose($fpNewFile); + fclose($fopen); + file_put_contents($this->_filename, file_get_contents($this->_filename.".new")); + unlink($this->_filename.".new"); + return $this; + } + + protected function _checkFile() + { + if (empty($this->_filename)) { + throw new \Exception("Un fichier doit être spécifié."); + } + $dir = dirname($this->_filename); + if (!is_file($this->_filename)) { + if (!is_writable($dir)) { + throw new \Exception("Pas d'accès en écriture sur le répertoire '".$dir."'."); + } + } elseif (!is_writable($this->_filename)) { + throw new \Exception("Pas d'accès en écriture sur le fichier '".$this->_filename."'."); + } + } +} \ No newline at end of file diff --git a/sources/app/models/Storage/File/User.php b/sources/app/models/Storage/File/User.php new file mode 100644 index 0000000..3b0ca83 --- /dev/null +++ b/sources/app/models/Storage/File/User.php @@ -0,0 +1,145 @@ +_filename = $filename; + $this->_checkFile(); + } + + public function fetchAll() + { + $users = array(); + if (is_file($this->_filename)) { + $fopen = fopen($this->_filename, "r"); + while (false !== $value = fgets($fopen)) { + $value = trim($value); + $user = new \App\User\User(); + $user->setPassword(substr($value, 0, 40)) + ->setUsername(substr($value, 40)); + $this->_loadUserOptions($user); + $users[] = $user; + } + fclose($fopen); + } + return $users; + } + + public function fetchByUsername($username) + { + $user = null; + if (is_file($this->_filename)) { + $fopen = fopen($this->_filename, "r"); + while (false !== $value = fgets($fopen)) { + $value = trim($value); + if (substr($value, 40) == $username) { + $user = new \App\User\User(); + $user->setPassword(substr($value, 0, 40)) + ->setUsername($username); + $this->_loadUserOptions($user); + break; + } + } + fclose($fopen); + } + return $user; + } + + public function save(\App\User\User $user) + { + if (!$this->fetchByUsername($user->getUsername()) || !is_file($this->_filename)) { + $fopen = fopen($this->_filename, "a"); + fputs($fopen, $user->getPassword().$user->getUsername()."\n"); + fclose($fopen); + } else { + $fopen = fopen($this->_filename, "r"); + $fpNewFile = fopen($this->_filename.".new", "w"); + flock($fopen, LOCK_EX); + while (false !== $value = fgets($fopen)) { + $value = trim($value); + if (substr($value, 40) == $user->getUsername()) { + $value = $user->getPassword().$user->getUsername(); + } + fputs($fpNewFile, $value."\n"); + } + fclose($fopen); + fclose($fpNewFile); + file_put_contents($this->_filename, file_get_contents($this->_filename.".new")); + unlink($this->_filename.".new"); + $this->_saveUserOptions($user); + } + return $this; + } + + public function delete(\App\User\User $user) + { + if (is_file($this->_filename)) { + $fopen = fopen($this->_filename, "r"); + $fpNewFile = fopen($this->_filename.".new", "w"); + while (false !== $value = fgets($fopen)) { + $value = trim($value); + if (substr($value, 40) != $user->getUsername()) { + fputs($fpNewFile, $value."\n"); + } + } + fclose($fopen); + fclose($fpNewFile); + file_put_contents($this->_filename, file_get_contents($this->_filename.".new")); + unlink($this->_filename.".new"); + $this->_deleteUserOptions($user); + } + return $this; + } + + protected function _checkFile() + { + if (empty($this->_filename)) { + throw new \Exception("Un fichier doit être spécifié."); + } + $dir = dirname($this->_filename); + if (!is_file($this->_filename)) { + if (!is_writable($dir)) { + throw new \Exception("Pas d'accès en écriture sur le répertoire '".$dir."'."); + } + } elseif (!is_writable($this->_filename)) { + throw new \Exception("Pas d'accès en écriture sur le fichier '".$this->_filename."'."); + } + } + + protected function _loadUserOptions(\App\User\User $user) + { + $dir = DOCUMENT_ROOT.DS."var".DS."configs"; + $filename = $dir.DS."user_".$user->getUsername().".json"; + if (is_file($filename)) { + $data = json_decode(trim(file_get_contents($filename)), true); + if ($data && is_array($data)) { + $user->setOptions($data); + } + } + return $this; + } + + protected function _saveUserOptions(\App\User\User $user) + { + $dir = DOCUMENT_ROOT.DS."var".DS."configs"; + if (!is_dir($dir)) { + mkdir($dir); + } + $filename = $dir.DS."user_".$user->getUsername().".json"; + file_put_contents($filename, json_encode($user->getOptions())); + return $this; + } + + protected function _deleteUserOptions(\App\User\User $user) + { + $dir = DOCUMENT_ROOT.DS."var".DS."configs"; + $filename = $dir.DS."user_".$user->getUsername().".json"; + if (is_file($filename)) { + unlink($filename); + } + return $this; + } +} \ No newline at end of file diff --git a/sources/app/models/Storage/User.php b/sources/app/models/Storage/User.php new file mode 100644 index 0000000..d7e1be0 --- /dev/null +++ b/sources/app/models/Storage/User.php @@ -0,0 +1,14 @@ +_tmp_dir = DOCUMENT_ROOT."/var/tmp"; + $this->_destination = DOCUMENT_ROOT; + } + + /** + * @param string $tmp_dir + * @return Updater + */ + public function setTmpDir($tmp_dir) + { + $this->_tmp_dir = $tmp_dir; + return $this; + } + + /** + * @return string + */ + public function getTmpDir() + { + return $this->_tmp_dir; + } + + /** + * @param string $destination + * @return Updater + */ + public function setDestination($destination) + { + $this->_destination = $destination; + return $this; + } + + /** + * @return string + */ + public function getDestination() + { + return $this->_destination; + } + + /** + * @param string $url_version + * @return Updater + */ + public function setUrlVersion($url_version) + { + $this->_url_version = $url_version; + return $this; + } + + /** + * @return string + */ + public function getUrlVersion() + { + return $this->_url_version; + } + + /** + * @param string $url_archive + * @return Updater + */ + public function setUrlArchive($url_archive) + { + $this->_url_archive = $url_archive; + return $this; + } + + /** + * @return string + */ + public function getUrlArchive() + { + return $this->_url_archive; + } + + public function getLastVersion() + { + $lastVersion = file_get_contents($this->getUrlVersion()); + if (preg_match('#return\s+"(.*)"\s*;#imsU', $lastVersion, $m)) { + return $m[1]; + } + throw new \Exception("Impossible de récupérer la dernière version."); + } + + public function installFiles($version) + { + $tmpZip = $this->_tmp_dir."/latest.zip"; + if (!is_dir($this->_tmp_dir)) { + mkdir($this->_tmp_dir); + } + if (!is_writable($this->_tmp_dir)) { + throw new \Exception("Impossible d'écrire dans '".$this->_tmp_dir."'"); + } + $archive = str_replace("%VERSION%", $version, $this->_url_archive); + if (!is_file($tmpZip) && !copy($archive, $tmpZip)) { + throw new \Exception("Impossible de récupérer l'archive."); + } + $zip = new \ZipArchive(); + if (!$zip->open($tmpZip)) { + throw new \Exception("L'archive semble erronée."); + } + // extraire l'archive + $zip->extractTo($this->_tmp_dir); + $zip->close(); + + // mise à jour des fichiers. + $this->_copyFiles($this->_tmp_dir."/LBCAlerte-".$version, $this->_destination); + rmdir($this->_tmp_dir."/LBCAlerte-".$version); + unlink($tmpZip); + } + + public function update($fromVersion, $toVersion) + { + // exécute les mises à jour + $directory = $this->_destination."/others/update"; + if (is_dir($directory)) { + $filenames = scandir($directory); + $filenames_php = array(); + foreach ($filenames AS $filename) { + if ($filename != "update.php" && false !== strpos($filename, ".php")) { + $filenames_php[basename($filename, ".php")] = $filename; + } + } + $versions = array_keys($filenames_php); + usort($versions, function ($a1, $a2) { + return version_compare($a1, $a2, "<") ? -1 : 1; + }); + foreach ($versions AS $version) { + if (version_compare($fromVersion, $version, "<") + && version_compare($toVersion, $version, ">=")) { + require $directory."/".$filenames_php[$version]; + $class = "Update_".str_replace(".", "", $version); + if (class_exists($class, false)) { + $class = new $class(); + $class->update(); + } + } + } + } + } + + protected function _copyFiles($dir, $to) + { + foreach (scandir($dir) AS $file) { + if ($file == "." || $file == "..") { + continue; + } + $destFile = $to."/".$file; + if (is_file($dir."/".$file)) { + rename($dir."/".$file, $destFile); + } elseif (is_dir($dir."/".$file)) { + if (!is_dir($destFile)) { + mkdir($destFile); + } + $this->_copyFiles($dir."/".$file, $destFile); + rmdir($dir."/".$file); + } + } + } +} diff --git a/sources/app/models/User/User.php b/sources/app/models/User/User.php new file mode 100644 index 0000000..ce4a2b2 --- /dev/null +++ b/sources/app/models/User/User.php @@ -0,0 +1,190 @@ +setId($options["id"]); + } + if (isset($options["username"])) { + $this->setUsername($options["username"]); + } + if (isset($options["password"])) { + $this->setPassword($options["password"]); + } + } + + /** + * @param int $id + * @return User + */ + public function setId($id) + { + $this->_id = $id; + return $this; + } + + /** + * @return int + */ + public function getId() + { + if (!$this->_id) { + return md5($this->_username); + } + return $this->_id; + } + + /** + * @param string $username + * @return User + */ + public function setUsername($username) + { + $this->_username = $username; + return $this; + } + + /** + * @return string + */ + public function getUsername() + { + return $this->_username; + } + + /** + * @param string $password + * @return User + */ + public function setPassword($password) + { + $this->_password = $password; + return $this; + } + + /** + * @return string + */ + public function getPassword() + { + return $this->_password; + } + + /** + * Retourne vrai si la notification SMS Free Mobile est activée. + * @return boolean + */ + public function hasSMSFreeMobile() + { + return false != $this->getOption("notification.freeMobile"); + } + + /** + * Retourne vrai si la notification SMS OVH est activée. + * @return boolean + */ + public function hasSMSOvh() + { + return false != $this->getOption("notification.ovh"); + } + + /** + * Retourne vrai si la notification Pushbullet est activée. + * @return boolean + */ + public function hasPushbullet() + { + return false != $this->getOption("notification.pushbullet"); + } + + /** + * Retourne vrai si la notification NotifyMyAndroid est activée. + * @return boolean + */ + public function hasNotifyMyAndroid() + { + return false != $this->getOption("notification.notifymyandroid"); + } + + /** + * Retourne vrai si la notification Pushover est activée. + * @return boolean + */ + public function hasPushover() + { + return false != $this->getOption("notification.pushover"); + } + + /** + * Indique si l'utilisateur est administrateur. + * @return boolean + */ + public function isAdmin() + { + return $this->getUsername() == "admin"; + } + + public function getOption($name, $default = null) + { + if (strpos($name, ".")) { + $options = explode(".", $name); + $nbOptions = count($options); + $current = $this->_options; + for ($i = 0; $i < $nbOptions; $i++) { + if (is_array($current) && isset($current[$options[$i]])) { + $current = $current[$options[$i]]; + } else { + break; + } + } + if ($i == $nbOptions) { + return $current; + } + return $default; + } + return isset($this->_options[$name])?$this->_options[$name]:$default; + } + + public function setOption($name, $value) + { + if (strpos($name, ".")) { + $options = explode(".", $name); + $nbOptions = count($options); + $current = $value; + for ($i = $nbOptions - 1; $i >= 0; $i--) { + $current = array($options[$i] => $current); + } + $this->_options = array_replace_recursive($this->_options, $current); + } else { + $this->_options[$name] = $value; + } + return $this; + } + + public function getOptions() + { + return $this->_options; + } + + public function setOptions(array $options) + { + $this->_options = $options; + return $this; + } + + public function mergeOptions(array $options) + { + $this->_options = array_replace_recursive($this->_options, $options); + return $this; + } +} \ No newline at end of file diff --git a/sources/app/rss/scripts/index.php b/sources/app/rss/scripts/index.php new file mode 100644 index 0000000..771f639 --- /dev/null +++ b/sources/app/rss/scripts/index.php @@ -0,0 +1,45 @@ + "", "price_min" => "", "price_max" => "", "price_strict" => false, + "cities" => "", "categories" => array() +); + +$categoryCollection = new \Lbc\CategoryCollection(); + +if ($_SERVER["REQUEST_METHOD"] == "POST") { + foreach ($_POST AS $name => $value) { + if (is_array($value)) { + $_POST[$name] = array_map("trim", $_POST[$name]); + } else { + $_POST[$name] = trim($_POST[$name]); + } + } + $values = array_merge($values, $_POST); + if (empty($values["url"])) { + $errors["url"] = "Ce champ est obligatoire."; + } + if ($values["price_min"] && $values["price_min"] != (int)$values["price_min"]) { + $errors["price"] = "Valeur de \"prix min\" non valide. "; + } + if ($values["price_max"] && $values["price_max"] != (int)$values["price_max"]) { + $errors["price"] .= "Valeur de \"prix max\" non valide."; + } + if (empty($errors)) { + $query = array("mod" => "rss", "a" => "refresh", "url" => $values["url"]); + if (!empty($values["price_min"])) { + $query["price_min"] = (int)$values["price_min"]; + } + if (!empty($values["price_max"])) { + $query["price_max"] = (int)$values["price_max"]; + } + if (!empty($values["cities"])) { + $query["cities"] = $values["cities"]; + } + if (!empty($values["categories"]) && is_array($values["categories"])) { + $query["categories"] = $values["categories"]; + } + $query["price_strict"] = isset($values["price_strict"])? + (int)(bool)$values["price_strict"]:0; + header("LOCATION: ./?".http_build_query($query)); + } +} \ No newline at end of file diff --git a/sources/app/rss/scripts/refresh.php b/sources/app/rss/scripts/refresh.php new file mode 100644 index 0000000..5b43af7 --- /dev/null +++ b/sources/app/rss/scripts/refresh.php @@ -0,0 +1,98 @@ +request($_GET["url"]); + +$filter = new \AdService\Filter($params); +$siteConfig = \AdService\SiteConfigFactory::factory($_GET["url"]); + +$ads = $parser->process( + $content, + $filter, + parse_url($_GET["url"], PHP_URL_SCHEME) +); + +$title = $siteConfig->getOption("site_name"); +$urlParams = parse_url($_GET["url"]); +if (!empty($urlParams["query"])) { + parse_str($urlParams["query"], $aQuery); + if (!empty($aQuery["q"])) { + $title .= " - ".$aQuery["q"]; + } +} + +$feeds = new RSS2; +$feeds->setTitle($siteConfig->getOption("site_name")); +$feeds->setLink($siteConfig->getOption("site_url")); +$feeds->setSelfLink( + !empty($_SERVER["HTTPS"]) && $_SERVER["HTTPS"] == "on"?"https":"http". + "://".$_SERVER["HTTP_HOST"].$_SERVER["REQUEST_URI"] +); +$feeds->setDescription("Flux RSS de la recherche : ".$_GET["url"]); +$feeds->setChannelElement("language", "fr-FR"); +// The date when this feed was lastly updated. The publication date is also set. +$feeds->setDate(date(DATE_RSS, time())); +$feeds->setChannelElement("pubDate", date(\DATE_RSS, strtotime("2013-04-06"))); +$feeds->addGenerator(); + +if (count($ads)) { + foreach ($ads AS $ad) { + $item = $feeds->createNewItem(); + $item->setTitle($ad->getTitle()); + $item->setLink($ad->getLink()); + $item->setDescription(require DOCUMENT_ROOT."/app/rss/views/rss-ad.phtml"); + if ($ad->getDate()) { + $item->setDate($ad->getDate()); + } + $item->setId(md5($ad->getId())); + $feeds->addItem($item); + } +} +$content = $feeds->generateFeed(); +file_put_contents($cache_filename, $content); +echo $content; + diff --git a/sources/app/rss/views/index.phtml b/sources/app/rss/views/index.phtml new file mode 100644 index 0000000..31d3634 --- /dev/null +++ b/sources/app/rss/views/index.phtml @@ -0,0 +1,70 @@ +
+

Génération d'un flux RSS

+
+
+
" size="75" placeholder="Ex: " />
+
+ +

Filtres supplémentaires (cliquez pour )

+
;"> +
+ +
+
+ + + +

+ +
+
+ + +
+
+
+ +
+
+
+

Attention : + ce filtre ne doit être utilisé que pour les recherches sur "toutes les catégories".

+ + fetchAll() AS $group => $categories) : ?> +
+

+ $category) : ?> + + +
+ +
+
+

+
\ No newline at end of file diff --git a/sources/app/rss/views/rss-ad.phtml b/sources/app/rss/views/rss-ad.phtml new file mode 100644 index 0000000..c242854 --- /dev/null +++ b/sources/app/rss/views/rss-ad.phtml @@ -0,0 +1,45 @@ + +getDate()) : ?> +Publié le getDate()); ?> +
+
+ + +Nom : getTitle(); ?> +getPrice()) : ?> +    Prix : getPrice(), 0, ',', ' '); ?> getCurrency(); ?> + + + +getCategory()) : ?> +
+Catégorie : getCategory(); ?> + +
+getCountry()) : ?> +Département : getCountry(); ?> +     + +getCity()) : ?>Ville : getCity(); ?> + +getOption("pro_visible")) : ?> +
Annonce de getProfessional()?'professionnel':'particulier.'; ?> + +
+getUrgent()) : ?> +urgent + + + +getThumbnailLink()) : ?> +
+ +
Pas de photo disponible. + + $userAuthed->getOption("notification"), + "unique_ads" => $userAuthed->getOption("unique_ads", false) +); +if (!is_array($params["notification"])) { + $params["notification"] = array(); +} +$form_values["notification"] = array_replace_recursive(array( + "freeMobile" => array( + "user" => "", + "key" => "", + ), + "notifymyandroid" => array( + "token" => "", + ), + "pushbullet" => array( + "token" => "", + ), + "ovh" => array( + "account" => "", + "login" => "", + "password" => "", + "from" => "", + "to" => "", + ), + "pushover" => array( + "token" => "", + "user_key" => "", + ), +), $params["notification"]); + +$errors = array(); +$errorsTest = array(); + +if ($_SERVER["REQUEST_METHOD"] == "POST") { + $params = array_merge($params, $_POST); + + // test config Free Mobile + foreach ($params["notification"] AS $section => $options) { + if (is_array($options)) { + $hasValue = false; + foreach ($options AS $name => $value) { + if (empty($value)) { + $errors["notification"][$section][$name] = "Ce champ doit être renseigné."; + } else { + $hasValue = true; + } + } + if (!$hasValue) { + unset($errors["notification"][$section]); + $params["notification"][$section] = false; + } + } + } + if (empty($errors["notification"])) { + unset($errors["notification"]); + } + + if (empty($errors)) { + if (!empty($_POST["testFreeMobile"])) { + $sms = \Message\AdapterFactory::factory("freeMobile", $params["notification"]["freeMobile"]); + try { + $sms->send("La notification SMS est fonctionnelle."); + } catch (Exception $e) { + $errorsTest["freeMobile"] = "Erreur lors de l'envoi du SMS : (".$e->getCode().") ".$e->getMessage(); + } + } elseif (!empty($_POST["testPushbullet"])) { + if (empty($_POST["notification"]["pushbullet"]["token"])) { + $errors["notification"]["pushbullet"]["token"] = "Veuillez renseigner la clé d'identification. "; + } else { + $sender = \Message\AdapterFactory::factory("pushbullet", $_POST["notification"]["pushbullet"]); + try { + $sender->send("La notification Pushbullet est fonctionnelle"); + } catch (Exception $e) { + $errorsTest["pushbullet"] = "Erreur lors de l'envoi de la notification : (".$e->getCode().") ".$e->getMessage(); + } + } + } elseif (!empty($_POST["testOvh"])) { + $sender = \Message\AdapterFactory::factory("SmsOvh", $params["notification"]["ovh"]); + try { + $sender->send("La notification SMS est fonctionnelle."); + } catch (Exception $e) { + $errorsTest["ovh"] = "Erreur lors de l'envoi du SMS : (".$e->getCode().") ".$e->getMessage(); + } + } elseif (!empty($_POST["testNotifyMyAndroid"])) { + if (empty($_POST["notification"]["notifymyandroid"]["token"])) { + $errors["notification"]["notifymyandroid"]["token"] = "Veuillez renseigner la clé d'identification."; + } else { + $sender = \Message\AdapterFactory::factory("notifymyandroid", $_POST["notification"]["notifymyandroid"]); + try { + $sender->send("La notification NotifyMyAndroid est fonctionnelle", array( + "title" => "Test alerte" + )); + } catch (Exception $e) { + $errorsTest["notifymyandroid"] = "Erreur lors de l'envoi de la notification : (".$e->getCode().") ".$e->getMessage(); + } + } + } elseif (!empty($_POST["testPushover"])) { + if (empty($_POST["notification"]["pushover"]["token"])) { + $errors["notification"]["pushover"]["token"] = "Veuillez renseigner la clé application."; + } elseif (empty($_POST["notification"]["pushover"]["user_key"])) { + $errors["notification"]["pushover"]["user_key"] = "Veuillez renseigner la clé utilisateur."; + } else { + $sender = \Message\AdapterFactory::factory("pushover", $_POST["notification"]["pushover"]); + try { + $sender->send("La notification Pushover est fonctionnelle"); + } catch (Exception $e) { + $errorsTest["pushover"] = "Erreur lors de l'envoi de la notification : (".$e->getCode().") ".$e->getMessage(); + } + } + } else { + $userAuthed->mergeOptions($params); + $userStorage->save($userAuthed); + $_SESSION["userSettingsSaved"] = true; + header("LOCATION: ./?mod=user&a=settings"); exit; + } + } +} + +$userSettingsSaved = isset($_SESSION["userSettingsSaved"]) && true === $_SESSION["userSettingsSaved"]; +unset($_SESSION["userSettingsSaved"]); diff --git a/sources/app/user/views/settings.phtml b/sources/app/user/views/settings.phtml new file mode 100644 index 0000000..c6cd0bb --- /dev/null +++ b/sources/app/user/views/settings.phtml @@ -0,0 +1,250 @@ +
+ +

+ Il y a au moins une erreur présente dans le formulaire.

+ +

+ Le test d'envoi a échoué. Voir ci-dessous pour le message d'erreur.

+ +

+ Les paramètres ont été enregistrés.

+ + +

Options générales

+
+
+ + +
+
+ +

Configuration SMS via Free Mobile

+
;"> +
+ +
+
+ " /> + +

+ +
+
+ +
+
+ " /> + +

+ +
+
+ +
+ +
+ +

+ +

Vous devriez recevoir un SMS contenant le message suivant: + « La notification SMS est fonctionnelle ».

+

Pensez à confirmer la configuration en cliquant sur "Enregistrer".

+ +
+ +
+ +

Configuration alerte via Pushbullet

+
;"> +
+ +
+
+ " /> + +

+ +
+
+ +
+ +
+ +

+ +

Vous devriez recevoir une notification contenant le message suivant: + « La notification Pushbullet est fonctionnelle ».

+

Pensez à confirmer la configuration en cliquant sur "Enregistrer".

+ +
+ +
+ +

Configuration SMS via OVH Telecom

+
;"> +
+ +
+
+ " placeholder="De la forme : sms-xxxxxxxx-x" /> + +

+ +
+
+ +
+
+ " /> + +

+ +
+
+ +
+
+ " /> + +

+ +
+
+ +
+
+ " /> + +

+ +
+
+ +
+
+ " placeholder="Forme international (Ex: +33605040301)" /> + +

+ +
+ +
+ +
+ +
+ +

+ +

Vous devriez recevoir un SMS contenant le message suivant: + « La notification SMS est fonctionnelle ».

+

Pensez à confirmer la configuration en cliquant sur "Enregistrer".

+ +
+ +
+ +

Configuration alerte via NotifyMyAndroid

+
;"> +
+ +
+
+ " /> + +

+ +
+
+ +
+ +
+ +

+ +

Vous devriez recevoir une notification contenant le message suivant: + « La notification NotifyMyAndroid est fonctionnelle ».

+

Pensez à confirmer la configuration en cliquant sur "Enregistrer".

+ +
+ +
+ +

Configuration alerte via Pushover

+
;"> +
+ +
+
+ " /> + +

+ +
+
+ +
+
+ " /> + +

+ +
+
+ +
+ +
+ +

+ +

Vous devriez recevoir une notification contenant le message suivant: + « La notification Pushover est fonctionnelle ».

+

Pensez à confirmer la configuration en cliquant sur "Enregistrer".

+ +
+ +
+ +

+ | annuler les modifications

+
diff --git a/sources/bootstrap.php b/sources/bootstrap.php new file mode 100644 index 0000000..58b5fba --- /dev/null +++ b/sources/bootstrap.php @@ -0,0 +1,188 @@ + array( + "appenders" => array("default"), + "level" => APPLICATION_ENV == "development"?"debug":"info" + ), + "appenders" => array( + "default" => array( + "class" => "LoggerAppenderRollingFile", + "layout" => array( + "class" => "LoggerLayoutPattern", + "params" => array( + "conversionPattern" => "%date %-5level %msg%n" + ) + ), + "params" => array( + "file" => DOCUMENT_ROOT."/var/log/info.log", + "maxFileSize" => "3MB", + "maxBackupIndex" => 5, + "append" => true + ) + ) + ) + )); + } + + protected function initConfig() + { + // valeurs par défaut. + $this->_config->set("proxy", "ip", ""); + $this->_config->set("proxy", "port", ""); + $this->_config->set("proxy", "user", ""); + $this->_config->set("proxy", "password", ""); + $this->_config->set("http", "user_agent", "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2.6) Gecko/20100628 Ubuntu/10.04 (lucid) Firefox/3.6.6"); + $this->_config->set("general", "check_start", 7); + $this->_config->set("general", "check_end", 24); + $this->_config->set("general", "version", 0); + $this->_config->set("general", "baseurl", ""); + $this->_config->set("storage", "type", "files"); + + // lit la configuration du fichier. + try { + $this->_config->read(); + } catch (Config_Lite_Exception_Runtime $e) { + return; + } + + if (isset($_SERVER["HTTP_HOST"])) { + $current_base_url = $this->_config->get("general", "baseurl", ""); + $base_url = "http"; + if (!empty($_SERVER["HTTPS"]) && $_SERVER["HTTPS"] != "off") { + $base_url .= "s"; + } + $base_url .= "://".$_SERVER["HTTP_HOST"]."/"; + if (!empty($_SERVER["REQUEST_URI"])) { + $request_uri = trim($_SERVER["REQUEST_URI"], "/"); + if (false !== $pos = strpos($request_uri, "?")) { + $request_uri = mb_substr($request_uri, 0, $pos); + } + if (false !== strpos($request_uri, ".php")) { + $request_uri = substr($request_uri, 0, strrpos($request_uri, "/")); + } + if ($request_uri) { + $base_url .= trim($request_uri, "/")."/"; + } + } + if ($base_url != $current_base_url) { + $this->_config->set("general", "baseurl", $base_url); + $this->_config->save(); + } + } + } + + public function getClient() + { + if (!$this->_client) { + $client = new HttpClientCurl(); + $proxy = $this->_config->get("proxy", null, array()); + if (!empty($proxy["ip"])) { + $client->setProxyIp($proxy["ip"]); + if (!empty($proxy["port"])) { + $client->setProxyPort($proxy["port"]); + } + } + if ($userAgent = $this->_config->get("http", "user_agent", "")) { + $client->setUserAgent($userAgent); + } + } + return $client; + } + + public function __construct() + { + } + + public function bootstrap(Config_Lite $config) + { + $this->_config = $config; + foreach (get_class_methods($this) AS $method) { + if (0 === strpos($method, "init")) { + $this->$method(); + } + } + } +} + +$config = new Config_Lite(DOCUMENT_ROOT."/var/config.ini"); + +$bootstrap = new Bootstrap(); +$bootstrap->bootstrap($config); +$userAuthed = null; + +// initialise le client HTTP. +$client = $bootstrap->getClient(); + +// si stockage en base de données, on initialise la connexion +if ("db" == $config->get("storage", "type", "files")) { + $options = array_merge(array( + "host" => "", + "user" => "", + "password" => "", + "dbname" => "" + ), $config->get("storage", "options")); + $dbConnection = new mysqli($options["host"], $options["user"], + $options["password"], $options["dbname"]); + unset($options); +} diff --git a/sources/check.php b/sources/check.php new file mode 100644 index 0000000..32f39ed --- /dev/null +++ b/sources/check.php @@ -0,0 +1,2 @@ +get("general", "version")) { + if ($module != "install") { + $module = "install"; + } +} elseif (isset($_GET["url"])) { // rendre compatible avec l'ancien système de flux RSS + $module = "rss"; + $action = "refresh"; +} + +if ($module != "install") { + $storageType = $config->get("storage", "type", "files"); + if ($storageType == "db") { + $userStorage = new \App\Storage\Db\User($dbConnection); + } else { + $userStorage = new \App\Storage\File\User(DOCUMENT_ROOT."/var/users.db"); + } + + // identification nécessaire + if ($module == "rss" && $action == "refresh") { + $auth = new Auth\Session($userStorage); + if (!$userAuthed = $auth->authenticate()) { + $auth = new Auth\Basic($userStorage); + if (!$userAuthed = $auth->authenticate()) { + header('WWW-Authenticate: Basic realm="Identification"'); + header('HTTP/1.0 401 Unauthorized'); + echo "Non autorisé."; + exit; + } + } + } else { + $auth = new Auth\Session($userStorage); + if (!$userAuthed = $auth->authenticate()) { + $module = "default"; + $action = "login"; + } + } + + $upgradeStarted = version_compare($currentVersion, APPLICATION_VERSION, "<"); + if ($upgradeStarted) { + if ($userAuthed && $userAuthed->isAdmin()) { + if ($module != "admin" || $action != "upgrade") { + header("LOCATION: ./?mod=admin&a=upgrade"); + exit; + } + } elseif ($action != "login") { + require DOCUMENT_ROOT."/app/default/views/upgrade.phtml"; + return; + } + } +} + + +$init = DOCUMENT_ROOT."/app/".$module."/init.php"; +$script = DOCUMENT_ROOT."/app/".$module."/scripts/".$action.".php"; +$view = DOCUMENT_ROOT."/app/".$module."/views/".$action.".phtml"; +$layout = DOCUMENT_ROOT."/app/".$module."/views/layout.phtml"; + +if (is_file($init)) { + require $init; +} +if (is_file($script)) { + require $script; +} +if (!is_file($layout)) { + $layout = DOCUMENT_ROOT."/app/default/views/layout.phtml"; +} + +ob_start(); +if (is_file($view)) { + require $view; +} +$content = ob_get_clean(); +if (isset($disableLayout) && $disableLayout == true) { + echo $content; +} else { + require $layout; +} + + + diff --git a/sources/lib/AdService/Ad.php b/sources/lib/AdService/Ad.php new file mode 100644 index 0000000..dd764bb --- /dev/null +++ b/sources/lib/AdService/Ad.php @@ -0,0 +1,287 @@ +_id = $id; + return $this; + } + + /** + * @return int + */ + public function getId() + { + return $this->_id; + } + + + /** + * @param string $link + * @return \AdService\Ad + */ + public function setLink($link) + { + $this->_link = $link; + return $this; + } + + /** + * @return string + */ + public function getLink() + { + return $this->_link; + } + + + /** + * @param string $link + * @return \AdService\Ad + */ + public function setLinkMobile($link) + { + $this->_link_mobile = $link; + return $this; + } + + /** + * @return string + */ + public function getLinkMobile() + { + return $this->_link_mobile; + } + + + /** + * @param string $title + * @return \AdService\Ad + */ + public function setTitle($title) + { + $this->_title = $title; + return $this; + } + + /** + * @return string + */ + public function getTitle() + { + return $this->_title; + } + + + /** + * @param string $description + * @return \AdService\Ad + */ + public function setDescription($description) + { + $this->_description = $description; + return $this; + } + + /** + * @return string + */ + public function getDescription() + { + return $this->_description; + } + + + /** + * @param int $price + * @return \AdService\Ad + */ + public function setPrice($price) + { +// $this->_price = (int) preg_replace('/[^0-9]*/', '', $price); + $this->_price = $price; + return $this; + } + + /** + * @return int + */ + public function getPrice() + { + return $this->_price; + } + + /** + * @param string $currency + * @return Ad + */ + public function setCurrency($currency) + { + $this->_currency = $currency; + return $this; + } + + /** + * @return string + */ + public function getCurrency() + { + return $this->_currency; + } + + + /** + * @param Zend_Date $date + * @return \AdService\Ad + */ + public function setDate($date) + { + $this->_date = $date; + return $this; + } + + /** + * @return string + */ + public function getDate() + { + return $this->_date; + } + + + /** + * @param string $category + * @return \AdService\Ad + */ + public function setCategory($category) + { + $this->_category = $category; + return $this; + } + + /** + * @return string + */ + public function getCategory() + { + return $this->_category; + } + + + /** + * @param string $county + * @return \AdService\Ad + */ + public function setCountry($county) + { + $this->_country = $county; + return $this; + } + + /** + * @return string + */ + public function getCountry() + { + return $this->_country; + } + + + /** + * @param string $city + * @return \AdService\Ad + */ + public function setCity($city) + { + $this->_city = $city; + return $this; + } + + /** + * @return string + */ + public function getCity() + { + return $this->_city; + } + + + /** + * @param bool $professional + * @return \AdService\Ad + */ + public function setProfessional($professional) + { + $this->_professional = $professional; + return $this; + } + + /** + * @return bool + */ + public function getProfessional() + { + return $this->_professional; + } + + + /** + * @param string $thumbail + * @return \AdService\Ad + */ + public function setThumbnailLink($thumbail) + { + $this->_thumbnail_link = $thumbail; + return $this; + } + + /** + * @return string + */ + public function getThumbnailLink() + { + return $this->_thumbnail_link; + } + + + /** + * @param bool $urgent + * @return \AdService\Ad + */ + public function setUrgent($urgent) + { + $this->_urgent = (bool)$urgent; + return $this; + } + + /** + * @return bool + */ + public function getUrgent() + { + return $this->_urgent; + } +} \ No newline at end of file diff --git a/sources/lib/AdService/Exception.php b/sources/lib/AdService/Exception.php new file mode 100644 index 0000000..daa4db8 --- /dev/null +++ b/sources/lib/AdService/Exception.php @@ -0,0 +1,8 @@ +setFromArray($options); + } + + public function isValid(Ad $ad) + { + if (!$ad->getPrice() && $this->price_strict) { + return false; + } + + if ($ad->getPrice()) { + if ($this->price_min != -1 && $ad->getPrice() < $this->price_min + || $this->price_max != -1 && $ad->getPrice() > $this->price_max) { + return false; + } + } + + $city = mb_strtolower($ad->getCity()); + $country = mb_strtolower($ad->getCountry()); + if ($this->cities && !in_array($city, $this->cities) && !in_array($country, $this->cities)) { + return false; + } + + if ($this->categories && !in_array($ad->getCategory(), $this->categories)) { + return false; + } + + return true; + } + + public function setFromArray(array $options) + { + foreach ($options AS $option => $value) { + $method = "set".str_replace(" ", "", ucwords( + str_replace("_", " ", $option))); + if (method_exists($this, $method)) { + $this->$method($value); + } + } + } + + /** + * @param int $min_id + * @return Filter + */ + public function setMinId($min_id) + { + $this->min_id = $min_id; + return $this; + } + + /** + * @return int + */ + public function getMinId() + { + return $this->min_id; + } + + /** + * @param array $exclude_ids + * @return Filter + */ + public function setExcludeIds($exclude_ids) + { + $this->exclude_ids = $exclude_ids; + return $this; + } + + /** + * @return array + */ + public function getExcludeIds() + { + return $this->exclude_ids; + } + + /** + * @param int $price_min + * @return Filter + */ + public function setPriceMin($price_min) + { + $this->price_min = $price_min; + return $this; + } + + /** + * @return int + */ + public function getPriceMin() + { + return $this->price_min; + } + + /** + * @param int $price_max + * @return Filter + */ + public function setPriceMax($price_max) + { + $this->price_max = $price_max; + return $this; + } + + /** + * @return int + */ + public function getPriceMax() + { + return $this->price_max; + } + + /** + * @param bool $price_strict + * @return Filter + */ + public function setPriceStrict($price_strict) + { + $this->price_strict = $price_strict; + return $this; + } + + /** + * @return bool + */ + public function getPriceStrict() + { + return $this->price_strict; + } + + /** + * @param array $cities + * @return Filter + */ + public function setCities(array $cities) + { + $this->cities = $cities; + return $this; + } + + /** + * @return array + */ + public function getCities() + { + return $this->cities; + } + + /** + * @param array $categories + * @return Filter + */ + public function setCategories(array $categories) + { + $this->categories = $categories; + return $this; + } + + /** + * @return array + */ + public function getCategories() + { + return $this->categories; + } +} \ No newline at end of file diff --git a/sources/lib/AdService/Parser/AbstractParser.php b/sources/lib/AdService/Parser/AbstractParser.php new file mode 100644 index 0000000..d1eaede --- /dev/null +++ b/sources/lib/AdService/Parser/AbstractParser.php @@ -0,0 +1,19 @@ + 1, "fév" => 2, "mars" => 3, "avr" => 4, + "mai" => 5, "juin" => 6, "juillet" => 7, "août" => 8, + "sept" => 9, "oct" => 10, "nov" => 11, + "déc" => 12 + ); + + protected $scheme; + + public function process($content, Filter $filter = null, $scheme = "http") { + if (!$content) { + return; + } + $this->scheme = $scheme; + $this->loadHTML($content); + + $timeToday = strtotime(date("Y-m-d")." 23:59:59"); + $dateYesterday = $timeToday - 24*3600; + $ads = array(); + + if ($filter) { + $exclude_ids = $filter->getExcludeIds(); + + /** + * Afin de garder une rétrocompatibilité, on prend en compte + * que $exclude_ids peut être numérique. + */ + if (!is_numeric($exclude_ids) && !is_array($exclude_ids)) { + unset($exclude_ids); + } + } + + $adNodes = $this->getElementsByTagName("a"); + + foreach ($adNodes AS $result) { + // est-ce bien une annonce ? + if (false === strpos($result->getAttribute("class"), "list_item")) { + continue; + } + + $ad = new Ad(); + $ad->setProfessional(false)->setUrgent(false); + + // pas d'ID, pas d'annonce + if (!preg_match('/([0-9]+)\.htm.*/', $result->getAttribute("href"), $m)) { + continue; + } + + // permet d'éliminer les annonces déjà envoyées. + if (isset($exclude_ids)) { + if (is_numeric($exclude_ids)) { + /** + * Si $exclude_ids est numérique, alors détection + * à l'ancienne. Quand on rencontre l'ID de la + * dernière annonce, on stoppe la boucle. + */ + if ($m[1] == $exclude_ids) { + break; + } + + } elseif (in_array($m[1], $exclude_ids)) { + continue; + } + } + + // permet d'éliminer les annonces déjà envoyées. + if ($filter && $m[1] <= $filter->getMinId()) { + continue; + } + + $ad->setLink($this->formatLink($result->getAttribute("href"))) + ->setId($m[1]) + ->setTitle($result->getAttribute("title")) + ->setLinkMobile(str_replace( + array("http://www.", "https://www."), + array("http://mobile.", "https://mobile."), + $ad->getLink() + )); + + // recherche de l'image + foreach ($result->getElementsByTagName("span") AS $node) { + if ($src = $node->getAttribute("data-imgsrc")) { + $ad->setThumbnailLink($this->formatLink($src)); + } + } + + $i = 0; + foreach ($result->getElementsByTagName("p") AS $node) { + $class = (string) $node->getAttribute("class"); + if (false !== strpos($class, "item_supp")) { + $value = trim($node->nodeValue); + if ($i == 0) { // catégorie + if (false !== strpos($value, "(pro)")) { + $ad->setProfessional(true); + } + $ad->setCategory(trim(str_replace("(pro)", "", $value))); + + } elseif ($i == 1) { // localisation + if (false !== strpos($value, "/")) { + $value = explode("/", $value); + $ad->setCountry(trim($value[1])) + ->setCity(trim($value[0])); + } else { + $ad->setCountry(trim($value)); + } + + } elseif ($i == 2) { // date de l'annonce + urgent + $spans = $node->getElementsByTagName("span"); + if ($spans->length > 0) { + $ad->setUrgent(true); + $node->removeChild($spans->item(0)); + $value = trim($node->nodeValue); + } + + $dateStr = preg_replace("#\s+#", " ", $value); + $aDate = explode(' ', $dateStr); + $aDate[1] = trim($aDate[1], ","); + if (false !== strpos($dateStr, 'Aujourd')) { + $time = strtotime(date("Y-m-d")." 00:00:00"); + } elseif (false !== strpos($dateStr, 'Hier')) { + $time = strtotime(date("Y-m-d")." 00:00:00"); + $time = strtotime("-1 day", $time); + } else { + if (!isset(self::$months[$aDate[1]])) { + continue; + } + $time = strtotime(date("Y")."-".self::$months[$aDate[1]]."-".$aDate[0]); + } + $aTime = explode(":", $aDate[count($aDate) - 1]); + $time += (int)$aTime[0] * 3600 + (int)$aTime[1] * 60; + if ($timeToday < $time) { + $time = strtotime("-1 year", $time); + } + $ad->setDate($time); + } + $i++; + } + } + + // recherche du prix + foreach ($result->getElementsByTagName("h3") AS $node) { + $class = (string) $node->getAttribute("class"); + if (false !== strpos($class, "item_price")) { + if (preg_match("#[0-9 ]+#", $node->nodeValue, $m)) { + $ad->setPrice((int)str_replace(" ", "", trim($m[0]))); + } + } + } + + // exclure les annonces ne correspondant pas au filtre. + if ($filter && !$filter->isValid($ad)) { + continue; + } + + $ads[$ad->getId()] = $ad; + } + + return $ads; + } + + protected function formatLink($link) + { + if (0 === strpos($link, "//")) { + $link = $this->scheme.":".$link; + } + return $link; + } +} diff --git a/sources/lib/AdService/Parser/Olx.php b/sources/lib/AdService/Parser/Olx.php new file mode 100644 index 0000000..c5f4d1d --- /dev/null +++ b/sources/lib/AdService/Parser/Olx.php @@ -0,0 +1,137 @@ + 1, "fév" => 2, "mars" => 3, "апр." => 4, + "mai" => 5, "juin" => 6, "juillet" => 7, "août" => 8, + "sept" => 9, "oct" => 10, "nov" => 11, + "déc" => 12 + ); + + public function process($content, Filter $filter = null) { + if (!$content) { + return; + } + + $content = str_replace("
", " ", $content); + $this->loadHTML($content); + + $timeToday = strtotime(date("Y-m-d")." 23:59:59"); + $dateYesterday = $timeToday - 24*3600; + $ads = array(); + + $tables = $this->getElementsByTagName("table"); + $tableOffers = null; + foreach ($tables AS $table) { + if (false !== strpos($table->getAttribute("id"), "offers_table")) { + $tableOffers = $table; + break; + } + } + if (!$tableOffers) { + return array(); + } + $adNodes = $tableOffers->getElementsByTagName("td"); + foreach ($adNodes AS $adNode) { + if (false === strpos($adNode->getAttribute("class"), "offer")) { + continue; + } + $ad = new Ad(); + $ad->setUrgent(false); + + // aucun indicateur pour savoir si c'est un pro ou non. + $ad->setProfessional(false); + + // permet d'éliminer les annonces déjà envoyées. + // @todo pour le moment, pas possible. Les IDs ne semblent pas + // numérique et incrémentals. +// if ($filter && $m[1] <= $filter->getMinId()) { +// continue; +// } + + $rows = $adNode->getElementsByTagName("tr"); + if (0 == $rows->length) { + continue; + } + $columns = $adNode->getElementsByTagName("td"); + + $row2_p = $rows->item(1)->getElementsByTagName("p"); + + // analyse de la date + $dateStr = preg_replace("#\s+#", " ", trim($row2_p->item(1)->nodeValue)); + if (!$dateStr) { + continue; + } + $aDate = explode(' ', $dateStr); + if (false !== strpos($dateStr, 'Сегодня')) { // aujourd'hui + $time = strtotime(date("Y-m-d")." 00:00:00"); + } elseif (false !== strpos($dateStr, 'Вчера')) { + $time = strtotime(date("Y-m-d")." 00:00:00"); + $time = strtotime("-1 day", $time); + } else { + if (!isset(self::$months[$aDate[1]])) { + continue; + } + $time = strtotime(date("Y")."-".self::$months[$aDate[1]]."-".$aDate[0]); + } + $timeStr = $aDate[count($aDate) - 1]; + if (false !== $pos = mb_strpos($dateStr, ":")) { + $time += (int)mb_substr($dateStr, $pos - 2, 2) * 3600; + $time += (int)mb_substr($dateStr, $pos + 1, 2) * 60; + if ($timeToday < $time) { + $time = strtotime("-1 year", $time); + } + } + $ad->setDate($time); + + // image + $img = $columns->item(0)->getElementsByTagName("img"); + if ($img->length) { + $ad->setThumbnailLink(str_replace("94x72", "644x461", $img->item(0)->getAttribute("src"))); + } + + // titre + lien + $link = $adNode->getElementsByTagName("h3")->item(0)->getElementsByTagName("a")->item(0); + if ($link) { + $ad->setTitle(trim($link->nodeValue)); + $ad->setLink($link->getAttribute("href")); + } + + // urgent + if (false !== strpos($adNode->nodeValue, "Срочно")) { + $ad->setUrgent(true); + } + + // lieu + $ad->setCity(trim($row2_p->item(0)->nodeValue)); + + // catégorie + $ad->setCategory(trim($columns->item(1)->getElementsByTagName("p")->item(0)->nodeValue)); + + if (!preg_match("#ID([^.]+)\.html#", $ad->getLink(), $m)) { + continue; + } + $ad->setId(base_convert($m[1], 32, 10)); + + $priceColumn = trim($columns->item(2)->nodeValue); + if (preg_match('#(?[0-9\s]+)\s+(?грн|\$|€)#imsU', $priceColumn, $m)) { + $ad->setPrice((int) str_replace(" ", "", $m["price"])) + ->setCurrency($m["currency"]); + } + + if ($filter && !$filter->isValid($ad)) { + continue; + } + + $ads[$ad->getId()] = $ad; + } + + return $ads; + } +} diff --git a/sources/lib/AdService/Parser/Seloger.php b/sources/lib/AdService/Parser/Seloger.php new file mode 100644 index 0000000..62d0ad4 --- /dev/null +++ b/sources/lib/AdService/Parser/Seloger.php @@ -0,0 +1,125 @@ +loadHTML($content); + + $timeToday = strtotime(date("Y-m-d")." 23:59:59"); + $dateYesterday = $timeToday - 24*3600; + $ads = array(); + + $sections = $this->getElementsByTagName("section"); + $section_results = null; + foreach ($sections AS $section) { + if (false !== strpos($section->getAttribute("class"), "liste_resultat")) { + $section_results = $section; + break; + } + } + if (!$section_results) { + return array(); + } + + if ($filter) { + $exclude_ids = $filter->getExcludeIds(); + + /** + * Afin de garder une rétrocompatibilité, on prend en compte + * que $exclude_ids peut être numérique. + */ + if (!is_numeric($exclude_ids) && !is_array($exclude_ids)) { + unset($exclude_ids); + } + } + + $adNodes = $section_results->getElementsByTagName("article"); + foreach ($adNodes AS $adNode) { + if (!$id = (int) $adNode->getAttribute("data-listing-id")) { + continue; + } + + // permet d'éliminer les annonces déjà envoyées. + if (isset($exclude_ids)) { + if (is_numeric($exclude_ids)) { + /** + * Si $exclude_ids est numérique, alors détection + * à l'ancienne. Quand on rencontre l'ID de la + * dernière annonce, on stoppe la boucle. + */ + if ($id == $exclude_ids) { + break; + } + + } elseif (in_array($id, $exclude_ids)) { + continue; + } + } + + $ad = new Ad(); + $ad->setUrgent(false) + ->setId($id); + + // aucun indicateur pour savoir si c'est un pro ou non. + $ad->setProfessional(false); + + // image + $imgs = $adNode->getElementsByTagName("img"); + if ($imgs->length) { + foreach ($imgs AS $img) { + if (false !== strpos($img->getAttribute("class"), "listing_photo")) { + $ad->setThumbnailLink( + str_replace( + array("c175", "c250"), + "b600", + $img->getAttribute("src")) + ); + break; + } + } + } + + // titre + lien + lieu + $link = $adNode->getElementsByTagName("h2")->item(0) + ->getElementsByTagName("a")->item(0); + if ($link) { + $city = $link->getElementsByTagName("span")->item(0); + if ($city) { + // lieu + $ad->setCity(trim($city->nodeValue)); + } + $ad->setTitle(trim($link->nodeValue)); + $ad->setLink($link->getAttribute("href")); + } + + $links = $adNode->getElementsByTagName("a"); + if ($links->length) { + foreach ($links AS $link) { + $classCSS = $link->getAttribute("class"); + if (false !== strpos($classCSS, "amount")) { + $ad->setPrice((int) preg_replace("#[^0-9]*#", "", $link->nodeValue)); + } + } + } + + if ($filter && !$filter->isValid($ad)) { + continue; + } + + $ads[$ad->getId()] = $ad; + } + return $ads; + } +} diff --git a/sources/lib/AdService/ParserFactory.php b/sources/lib/AdService/ParserFactory.php new file mode 100644 index 0000000..a45708d --- /dev/null +++ b/sources/lib/AdService/ParserFactory.php @@ -0,0 +1,24 @@ +$name) ? $this->$name : null; + } +} \ No newline at end of file diff --git a/sources/lib/AdService/SiteConfig/Lbc.php b/sources/lib/AdService/SiteConfig/Lbc.php new file mode 100644 index 0000000..4730300 --- /dev/null +++ b/sources/lib/AdService/SiteConfig/Lbc.php @@ -0,0 +1,9 @@ +_storage = $storage; + } + + /** + * @param string $username + * @return Basic + */ + public function setUsername($username) + { + $this->_username = $username; + return $this; + } + + /** + * @return string + */ + public function getUsername() + { + return $this->_username; + } + + /** + * @param string $password + * @return Basic + */ + public function setPassword($password) + { + $this->_password = $password; + return $this; + } + + /** + * @return string + */ + public function getPassword() + { + return $this->_password; + } + + public function authenticate() + { + if (!$this->_username || !$this->_password) { + return null; + } + $user = $this->_storage->fetchByUsername($this->_username); + if ($user && $user->getPassword() == $this->_password) { + return $user; + } + return null; + } + + public function clear() {} +} \ No newline at end of file diff --git a/sources/lib/Auth/Basic.php b/sources/lib/Auth/Basic.php new file mode 100644 index 0000000..8bf4ace --- /dev/null +++ b/sources/lib/Auth/Basic.php @@ -0,0 +1,19 @@ +_username && isset($_SERVER["PHP_AUTH_USER"])) { + $this->setUsername($_SERVER["PHP_AUTH_USER"]); + } + if (!$this->_password && isset($_SERVER["PHP_AUTH_PW"])) { + $this->setPassword(sha1($_SERVER["PHP_AUTH_PW"])); + } + parent::__construct($storage); + } +} \ No newline at end of file diff --git a/sources/lib/Auth/Session.php b/sources/lib/Auth/Session.php new file mode 100644 index 0000000..420ceee --- /dev/null +++ b/sources/lib/Auth/Session.php @@ -0,0 +1,45 @@ +_username = $_SESSION["lbcauth"]["username"]; + } + if (isset($_SESSION["lbcauth"]["password"])) { + $this->_password = $_SESSION["lbcauth"]["password"]; + } + } + parent::__construct($storage); + } + + public function __destruct() + { + session_write_close(); + } + + public function clear() + { + unset($_SESSION["lbcauth"]); + } + + public function authenticate() + { + if ($user = parent::authenticate()) { + $_SESSION["lbcauth"] = array( + "username" => $user->getUsername(), + "password" => $user->getPassword() + ); + return $user; + } + return null; + } +} \ No newline at end of file diff --git a/sources/lib/Config/Lite.php b/sources/lib/Config/Lite.php new file mode 100644 index 0000000..28b2ec6 --- /dev/null +++ b/sources/lib/Config/Lite.php @@ -0,0 +1,838 @@ + + * @copyright 2010-2011 + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version SVN: $Id$ + * @link https://github.com/pce/config_lite + */ + +require_once 'Config/Lite/Exception.php'; +require_once 'Config/Lite/Exception/InvalidArgument.php'; +require_once 'Config/Lite/Exception/Runtime.php'; +require_once 'Config/Lite/Exception/UnexpectedValue.php'; + +/** + * Config_Lite Class + * + * read and save ini text files. + * Config_Lite has the native PHP function + * `parse_ini_file' under the hood. + * The API is inspired by Python's ConfigParser. + * A "Config_Lite" file consists of + * "name = value" entries and sections, + * "[section]" + * followed by "name = value" entries + * + * @category Configuration + * @package Config_Lite + * @author Patrick C. Engel + * @copyright 2010-2011 + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @link https://github.com/pce/config_lite + */ +class Config_Lite implements ArrayAccess, IteratorAggregate, Countable, Serializable +{ + /** + * sections, holds the config sections + * + * @var array + */ + protected $sections; + /** + * filename + * + * @var string + */ + protected $filename; + /** + * _booleans - alias of bool in a representable Configuration String Format + * + * @var array + */ + private $_booleans = array('1' => true, 'on' => true, + 'true' => true, 'yes' => true, + '0' => false, 'off' => false, + 'false' => false, 'no' => false); + + /** + * line-break chars, default *x: "\n", windows: "\r\n" + * + * @var string + */ + protected $linebreak = "\n"; + + /** + * parseSections - if true, sections will be processed + * + * @var bool + */ + protected $processSections = true; + + /** + * quote Strings - if true, + * writes ini files with doublequoted strings + * + * @var bool + */ + protected $quoteStrings = true; + + /** + * the read method parses the optional given filename + * or already setted filename. + * + * this method uses the native PHP function + * parse_ini_file behind the scenes. + * + * @param string $filename Filename + * + * @return void + * @throws Config_Lite_Exception_Runtime when file not found + * @throws Config_Lite_Exception_Runtime when file is not readable + * @throws Config_Lite_Exception_Runtime when parse ini file failed + */ + public function read($filename = null) + { + if (null === $filename) { + $filename = $this->filename; + } else { + $this->filename = $filename; + } + if (!file_exists($filename)) { + throw new Config_Lite_Exception_Runtime('file not found: ' . $filename); + } + if (!is_readable($filename)) { + throw new Config_Lite_Exception_Runtime( + 'file not readable: ' . $filename + ); + } + $this->sections = parse_ini_file($filename, $this->processSections); + if (false === $this->sections) { + throw new Config_Lite_Exception_Runtime( + 'failure, can not parse the file: ' . $filename + ); + } + } + /** + * save the object to the already setted filename + * (active record style) + * + * @return bool + */ + public function save() + { + return $this->write($this->filename, $this->sections); + } + /** + * sync the file to the object + * + * like `save', + * but after written the data, reads the data back into the object. + * The method is inspired by QTSettings. + * Ideal for testing. + * + * @return void + * @throws Config_Lite_Exception_Runtime when file is not set, + * write or readable + */ + public function sync() + { + if (!isset($this->filename)) { + throw new Config_Lite_Exception_Runtime('no filename set.'); + } + if (!is_array($this->sections)) { + $this->sections = array(); + } + if ($this->write($this->filename, $this->sections)) { + $this->read($this->filename); + } + } + + /** + * detect Type "bool" by String Value to keep those "untouched" + * + * @param string $value value + * + * @return bool + */ + protected function isBool($value) + { + return in_array($value, $this->_booleans); + } + + /** + * normalize a Value by determining the Type + * + * @param string $value value + * + * @return string + */ + protected function normalizeValue($value) + { + if (is_bool($value)) { + $value = $this->toBool($value); + return $value; + } elseif (is_numeric($value)) { + return $value; + } + if ($this->quoteStrings) { + $value = '"' . $value . '"'; + } + return $value; + } + + /** + * generic write ini config file, to save use `save'. + * + * writes the global options and sections with normalized Values, + * that means "bool" values to human readable representation, + * doublequotes strings and numeric values without any quotes. + * prepends a php exit if suffix is php, + * it is valid to write an empty Config file, + * this method is used by save and is public for explicit usage, + * eg. if you do not want to hold the whole configuration in the object. + * + * @param string $filename filename + * @param array $sectionsarray array with sections + * + * @return bool + * @throws Config_Lite_Exception_Runtime when file is not writeable + * @throws Config_Lite_Exception_Runtime when write failed + */ + public function write($filename, $sectionsarray) + { + $content = $this->buildOutputString($sectionsarray); + if (false === file_put_contents($filename, $content, LOCK_EX)) { + throw new Config_Lite_Exception_Runtime( + sprintf( + 'failed to write file `%s\' for writing.', $filename + ) + ); + } + return true; + } + + /** + * Generated the output of the ini file, suitable for echo'ing or + * writing back to the ini file. + * + * @param string $sectionsarray array of ini data + * + * @return string + */ + protected function buildOutputString($sectionsarray) + { + $content = ''; + $sections = ''; + $globals = ''; + if (!empty($sectionsarray)) { + // 2 loops to write `globals' on top, alternative: buffer + foreach ($sectionsarray as $section => $item) { + if (!is_array($item)) { + $value = $this->normalizeValue($item); + $globals .= $section . ' = ' . $value . $this->linebreak; + } + } + $content .= $globals; + foreach ($sectionsarray as $section => $item) { + if (is_array($item)) { + $sections .= $this->linebreak + . "[" . $section . "]" . $this->linebreak; + foreach ($item as $key => $value) { + if (is_array($value)) { + foreach ($value as $arrkey => $arrvalue) { + $arrvalue = $this->normalizeValue($arrvalue); + $arrkey = $key . '[' . $arrkey . ']'; + $sections .= $arrkey . ' = ' . $arrvalue + . $this->linebreak; + } + } else { + $value = $this->normalizeValue($value); + $sections .= $key . ' = ' . $value . $this->linebreak; + } + } + } + } + $content .= $sections; + } + return $content; + } + + /** + * converts string to a representable Config Bool Format + * + * @param string $value value + * + * @return string + * @throws Config_Lite_Exception_UnexpectedValue when format is unknown + */ + public function toBool($value) + { + if ($value === true) { + return 'yes'; + } + return 'no'; + } + + /** + * returns a stripslashed string + * + * @param string $sec Section + * @param string $key Key + * @param mixed $default default return value + * + * @return string + * @throws Config_Lite_Exception_Runtime when config is empty + * and no default value is given + * @throws Config_Lite_Exception_UnexpectedValue key not found + * and no default value is given + */ + public function getString($sec, $key, $default = null) + { + if ((null === $this->sections) && (null === $default)) { + throw new Config_Lite_Exception_Runtime( + 'configuration seems to be empty, no sections.' + ); + } + if ((null === $sec) && array_key_exists($key, $this->sections)) { + return stripslashes($this->sections[$key]); + } + if (array_key_exists($key, $this->sections[$sec])) { + return stripslashes($this->sections[$sec][$key]); + } + if (null !== $default) { + return $default; + } + throw new Config_Lite_Exception_UnexpectedValue( + 'key not found, no default value given.' + ); + } + + /** + * get an option by section, a global option or all sections and options + * + * to get an option by section, call get with a section and the option. + * To get a global option call `get' with null as section. + * Just call `get' without any parameters to get all sections and options. + * The third parameter is an optional default value to return, + * if the option is not set, this is practical when dealing with + * editable files, to keep an application stable with default settings. + * + * @param string $sec Section|null - null to get global option + * @param string $key Key + * @param mixed $default return default value if is $key is not set + * + * @return mixed + * @throws Config_Lite_Exception when config is empty + * and no default value is given + * @throws Config_Lite_Exception_UnexpectedValue key not found + * and no default value is given + */ + public function get($sec = null, $key = null, $default = null) + { + // handle get without parameters, because we can + if ((null === $sec) && (null === $key) && (null === $default)) { + return $this; // arrayaccess or $this->sections; + } + if ((null !== $sec) && array_key_exists($sec, $this->sections) + && isset($this->sections[$sec][$key]) + ) { + return $this->sections[$sec][$key]; + } + // global value + if ((null === $sec) && array_key_exists($key, $this->sections)) { + return $this->sections[$key]; + } + // section + if ((null === $key) && array_key_exists($sec, $this->sections)) { + return $this->sections[$sec]; + } + // all sections + if (null === $sec && array_key_exists($sec, $this->sections)) { + return $this->sections; + } + if (null !== $default) { + return $default; + } + throw new Config_Lite_Exception_UnexpectedValue( + 'key not found, no default value given.' + ); + } + + /** + * returns a boolean for strict equality comparison + * + * returns "on", "yes", 1, "true" as TRUE + * and no given value or "off", "no", 0, "false" as FALSE + * + * @param string $sec Section + * @param string $key Key + * @param bool $default return default value if is $key is not set + * + * @return bool + * @throws Config_Lite_Exception_Runtime when the configuration is empty + * and no default value is given + * @throws Config_Lite_Exception_InvalidArgument when is not a boolean + * and no default array is given + * @throws Config_Lite_Exception_UnexpectedValue when key not found + * and no default array is given + */ + public function getBool($sec, $key, $default = null) + { + if ((null === $this->sections) && (null === $default)) { + throw new Config_Lite_Exception_Runtime( + 'configuration seems to be empty (no sections),' + . 'and no default value given.' + ); + } + if ((null === $sec)) { + if (array_key_exists($key, $this->sections)) { + if (empty($this->sections[$key])) { + return false; + } + $value = strtolower($this->sections[$key]); + if (!in_array($value, $this->_booleans) && (null === $default)) { + throw new Config_Lite_Exception_InvalidArgument( + sprintf( + 'Not a boolean: %s, and no default value given.', + $value + ) + ); + } else { + return $this->_booleans[$value]; + } + } + } + if (array_key_exists($key, $this->sections[$sec])) { + if (empty($this->sections[$sec][$key])) { + return false; + } + $value = strtolower($this->sections[$sec][$key]); + if (!in_array($value, $this->_booleans) && (null === $default)) { + throw new Config_Lite_Exception_InvalidArgument( + sprintf( + 'Not a boolean: %s, and no default value given.', + $value + ) + ); + } else { + return $this->_booleans[$value]; + } + } + if (null !== $default) { + return $default; + } + throw new Config_Lite_Exception_UnexpectedValue( + 'option not found, no default value given.' + ); + } + + /** + * returns an array of options of the given section + * + * @param string $sec Section + * @param array $default return default array if $sec is not set + * + * @return array + * @throws Config_Lite_Exception_Runtime when config is empty + * and no default array is given + * @throws Config_Lite_Exception_UnexpectedValue when key not found + * and no default array is given + */ + public function getSection($sec, $default = null) + { + if ((null === $this->sections) && (null === $default)) { + throw new Config_Lite_Exception_Runtime( + 'configuration seems to be empty, no sections.' + ); + } + if (isset($this->sections[$sec])) { + return $this->sections[$sec]; + } + if ((null !== $default) && is_array($default)) { + return $default; + } + throw new Config_Lite_Exception_UnexpectedValue( + 'section not found, no default array given.' + ); + } + + /** + * returns true if the given section exists, otherwise false + * + * @param string $sec Section + * + * @return bool + */ + public function hasSection($sec) + { + if (isset($this->sections[$sec]) && is_array($this->sections[$sec])) { + return true; + } + return false; + } + + /** + * tests if a section or an option of a section exists + * + * @param string $sec Section + * @param string $key Key + * + * @return bool + */ + public function has($sec, $key=null) + { + if (!$this->hasSection($sec)) { + return false; + } + if ((null !== $key) && isset($this->sections[$sec][$key])) { + return true; + } + return false; + } + + /** + * remove a section or an option of a section + * + * @param string $sec Section + * @param string $key Key + * + * @return void + * @throws Config_Lite_Exception_UnexpectedValue when given Section not exists + */ + public function remove($sec, $key=null) + { + if (null === $key) { + $this->removeSection($sec); + return; + } + // global value + if (null === $sec && $key !== null) { + if (!isset($this->sections[$key])) { + throw new Config_Lite_Exception_UnexpectedValue( + 'No such global Value.' + ); + return; + } + unset($this->sections[$key]); + return; + } + if (!isset($this->sections[$sec])) { + throw new Config_Lite_Exception_UnexpectedValue('No such Section.'); + } + unset($this->sections[$sec][$key]); + } + + /** + * remove section by name + * + * @param string $sec Section + * + * @return void + * @throws Config_Lite_Exception_UnexpectedValue when given Section not exists + */ + public function removeSection($sec) + { + if (!isset($this->sections[$sec])) { + throw new Config_Lite_Exception_UnexpectedValue('No such Section.'); + } + unset($this->sections[$sec]); + } + + /** + * removes all sections and global options + * + * @return void + */ + public function clear() + { + $this->sections = array(); + } + + /** + * like set, but adds slashes to the value + * + * creates new section if necessary and overrides existing keys. + * + * @param string $sec Section + * @param string $key Key + * @param mixed $value Value + * + * @return $this + * @throws Config_Lite_Exception_InvalidArgument when given key is an array + */ + public function setString($sec, $key, $value = null) + { + if (null !== $value) { + $value = addslashes($value); + } + $this->set($sec, $key, $value); + return $this; + } + + /** + * to add key/value pairs + * + * creates new section if necessary and overrides existing keys. + * To set a global, "sectionless" value, call set with null as section. + * + * @param string $sec Section + * @param string $key Key + * @param mixed $value Value + * + * @throws Config_Lite_Exception when given key is an array + * @return $this + */ + public function set($sec, $key, $value = null) + { + if (!is_array($this->sections)) { + $this->sections = array(); + } + if (is_array($key) || is_array($sec)) { + throw new Config_Lite_Exception_InvalidArgument( + 'string key expected, but array given.' + ); + } + if (null === $sec) { + $this->sections[$key] = $value; + } else { + $this->sections[$sec][$key] = $value; + } + return $this; + } + + /** + * set a given array with key/value pairs to a section, + * creates a new section if necessary. + * + * @param string $sec Section + * @param array $pairs Keys and Values as Array ('key' => 'value') + * + * @throws Config_Lite_Exception_InvalidArgument array $pairs expected + * @return $this + */ + public function setSection($sec, $pairs) + { + if (!is_array($this->sections)) { + $this->sections = array(); + } + if (!is_array($pairs)) { + throw new Config_Lite_Exception_InvalidArgument('array expected.'); + } + $this->sections[$sec] = $pairs; + return $this; + } + + /** + * filename to read or save + * + * the full filename with suffix, ie. `[PATH/].ini'. + * you can also set the filename as parameter to the constructor. + * + * @param string $filename Filename + * + * @return $this + */ + public function setFilename($filename) + { + $this->filename = $filename; + return $this; + } + + /** + * returns the current filename + * + * @return string + */ + public function getFilename() + { + return $this->filename; + } + + /** + * set the line break (newline) chars + * + * line-break defaults to Unix Newline "\n", + * set to support other linebreaks, eg. windows user + * textfiles "\r\n" + * + * @param string $linebreakchars chars + * + * @return $this + */ + public function setLinebreak($linebreakchars) + { + $this->linebreak = $linebreakchars; + return $this; + } + + /** + * Sets whether or not sections should be processed + * + * If true, values for each section will be placed into + * a sub-array for the section. If false, all values will + * be placed in the global scope. + * + * @param bool $processSections - if true, sections will be processed + * + * @return $this + */ + public function setProcessSections($processSections) + { + $this->processSections = $processSections; + return $this; + } + + /** + * Sets whether or not to doubleQuote + * + * If true, everything but bool and numeric + * values get doublequoted. + * + * @param bool $quoteStrings - if true, Strings get doubleQuoted + * + * @return $this + */ + public function setQuoteStrings($quoteStrings) + { + $this->quoteStrings = $quoteStrings; + return $this; + } + + /** + * text presentation of the config object + * + * since a empty config is valid, + * it would return a empty string in that case. + * + * @throws Config_Lite_Exception_Runtime + * @return string + */ + public function __toString() + { + return $this->buildOutputString($this->sections); + } + + /** + * implemented for interface ArrayAccess + * + * @param string $offset section, implemented by ArrayAccess + * @param mixed $value KVP, implemented by ArrayAccess + * + * @return void + */ + public function offsetSet($offset, $value) + { + $this->sections[$offset] = $value; + } + + /** + * implemented for interface ArrayAccess + * + * @param string $offset - section, implemented by ArrayAccess + * + * @return bool + */ + public function offsetExists($offset) + { + return isset($this->sections[$offset]); + } + + /** + * implemented for interface ArrayAccess + * + * @param string $offset - section, implemented by ArrayAccess + * + * @return void + */ + public function offsetUnset($offset) + { + unset($this->sections[$offset]); + } + + /** + * implemented for interface ArrayAccess + * + * @param string $offset - section, implemented by ArrayAccess + * + * @return mixed + */ + public function offsetGet($offset) + { + if (array_key_exists($offset, $this->sections)) { + return $this->sections[$offset]; + } + return null; + } + /** + * implemented for interface IteratorAggregate + * + * @see http://www.php.net/~helly/php/ext/spl/interfaceIterator.html + * @return Iterator + */ + public function getIterator() + { + return new ArrayIterator($this->sections); + } + + /** + * implemented for interface Countable + * + * @see http://php.net.countable + * @return int + */ + public function count() + { + return count($this->sections); + } + + /** + * implemented for interface Serializable + * + * @see http://php.net/manual/en/class.serializable.php + * @return int + */ + public function serialize() + { + return serialize($this->sections); + } + + /** + * implemented for interface Serializable + * + * @param string $serializedData for instance + * + * @return void + */ + public function unserialize($serializedData) + { + $sections = unserialize($serializedData); + $this->sections = $sections; + } + + /** + * takes an optional filename, if the file exists, also reads it. + * + * the `save' and `read' methods relies on a setted filename, + * but you can also use `setFilename' to set the filename. + * + * @param string $filename - "INI Style" Text Config File + */ + public function __construct($filename = null) + { + $this->sections = array(); + if (null !== $filename) { + $this->setFilename($filename); + if (file_exists($filename)) { + $this->read($filename); + } + } + } +} diff --git a/sources/lib/Config/Lite/Exception.php b/sources/lib/Config/Lite/Exception.php new file mode 100644 index 0000000..507169e --- /dev/null +++ b/sources/lib/Config/Lite/Exception.php @@ -0,0 +1,34 @@ + + * @copyright 2010-2011 + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version SVN: $Id$ + * @link https://github.com/pce/config_lite + */ + + +/** + * Config_Lite_Exception + * + * Interface implemented by Exceptions + * + * @category Configuration + * @package Config_Lite + * @author Patrick C. Engel + * @copyright 2010-2011 + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version Release: 0.1.5 + * @link https://github.com/pce/config_lite + */ + +interface Config_Lite_Exception +{ +} diff --git a/sources/lib/Config/Lite/Exception/InvalidArgument.php b/sources/lib/Config/Lite/Exception/InvalidArgument.php new file mode 100644 index 0000000..42dbdac --- /dev/null +++ b/sources/lib/Config/Lite/Exception/InvalidArgument.php @@ -0,0 +1,35 @@ + + * @copyright 2010-2011 + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version SVN: $Id$ + * @link https://github.com/pce/config_lite + */ + + +/** + * Config_Lite_Exception_InvalidArgument + * + * implements Config_Lite_Exception + * + * @category Configuration + * @package Config_Lite + * @author Patrick C. Engel + * @copyright 2010-2011 + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version Release: 0.1.5 + * @link https://github.com/pce/config_lite + */ +class Config_Lite_Exception_InvalidArgument + extends RuntimeException + implements Config_Lite_Exception +{ +} diff --git a/sources/lib/Config/Lite/Exception/Runtime.php b/sources/lib/Config/Lite/Exception/Runtime.php new file mode 100644 index 0000000..379c29d --- /dev/null +++ b/sources/lib/Config/Lite/Exception/Runtime.php @@ -0,0 +1,36 @@ + + * @copyright 2010-2011 + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version SVN: $Id$ + * @link https://github.com/pce/config_lite + */ + + +/** + * Config_Lite_Exception_Runtime + * + * implements Config_Lite_Exception + * + * @category Configuration + * @package Config_Lite + * @author Patrick C. Engel + * @copyright 2010-2011 + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version Release: 0.1.5 + * @link https://github.com/pce/config_lite + */ + +class Config_Lite_Exception_Runtime + extends RuntimeException + implements Config_Lite_Exception +{ +} diff --git a/sources/lib/Config/Lite/Exception/UnexpectedValue.php b/sources/lib/Config/Lite/Exception/UnexpectedValue.php new file mode 100644 index 0000000..88a1b4e --- /dev/null +++ b/sources/lib/Config/Lite/Exception/UnexpectedValue.php @@ -0,0 +1,36 @@ + + * @copyright 2010-2011 + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version SVN: $Id$ + * @link https://github.com/pce/config_lite + */ + + +/** + * Config_Lite_Exception_UnexpectedValue + * + * implements Config_Lite_Exception + * + * @category Configuration + * @package Config_Lite + * @author Patrick C. Engel + * @copyright 2010-2011 + * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 + * @version Release: 0.1.5 + * @link https://github.com/pce/config_lite + */ + +class Config_Lite_Exception_UnexpectedValue + extends UnexpectedValueException + implements Config_Lite_Exception +{ +} diff --git a/sources/lib/FeedWriter/ATOM.php b/sources/lib/FeedWriter/ATOM.php new file mode 100644 index 0000000..7072b90 --- /dev/null +++ b/sources/lib/FeedWriter/ATOM.php @@ -0,0 +1,38 @@ + + * + * This file is part of the "Universal Feed Writer" project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/** + * Wrapper for creating ATOM feeds + * + * @package UniversalFeedWriter + */ +class Atom extends Feed +{ + /** + * {@inheritdoc} + */ + function __construct() + { + parent::__construct(Feed::ATOM); + } + +} diff --git a/sources/lib/FeedWriter/COPYING b/sources/lib/FeedWriter/COPYING new file mode 100644 index 0000000..94a9ed0 --- /dev/null +++ b/sources/lib/FeedWriter/COPYING @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/sources/lib/FeedWriter/Feed.php b/sources/lib/FeedWriter/Feed.php new file mode 100644 index 0000000..a0efd8e --- /dev/null +++ b/sources/lib/FeedWriter/Feed.php @@ -0,0 +1,869 @@ + + * Copyright (C) 2010-2013 Michael Bemmerl + * + * This file is part of the "Universal Feed Writer" project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +// RSS 0.90 Officially obsoleted by 1.0 +// RSS 0.91, 0.92, 0.93 and 0.94 Officially obsoleted by 2.0 +// So, define constants for RSS 1.0, RSS 2.0 and ATOM + +/** + * Universal Feed Writer class + * + * Generate RSS 1.0, RSS2.0 and ATOM Feeds + * + * @package UniversalFeedWriter + * @author Anis uddin Ahmad + * @link http://www.ajaxray.com/projects/rss + */ +abstract class Feed +{ + const RSS1 = 'RSS 1.0'; + const RSS2 = 'RSS 2.0'; + const ATOM = 'ATOM'; + + /** + * Collection of all channel elements + */ + private $channels = array(); + + /** + * Collection of items as object of \FeedWriter\Item class. + */ + private $items = array(); + + /** + * Store some other version wise data + */ + private $data = array(); + + /** + * The tag names which have to encoded as CDATA + */ + private $CDATAEncoding = array(); + + /** + * Collection of XML namespaces + */ + private $namespaces = array(); + + /** + * Contains the format of this feed. + */ + private $version = null; + + /** + * Constructor + * + * If no version is given, a feed in RSS 2.0 format will be generated. + * + * @param constant the version constant (RSS1/RSS2/ATOM). + */ + protected function __construct($version = FeedWriter::RSS2) + { + $this->version = $version; + + // Setting default encoding + $this->encoding = 'utf-8'; + + // Setting default value for essential channel elements + $this->channels['title'] = $version . ' Feed'; + $this->channels['link'] = 'http://www.ajaxray.com/blog'; + + // Add some default XML namespaces + $this->namespaces['content'] = 'http://purl.org/rss/1.0/modules/content/'; + $this->namespaces['wfw'] = 'http://wellformedweb.org/CommentAPI/'; + $this->namespaces['atom'] = 'http://www.w3.org/2005/Atom'; + $this->namespaces['rdf'] = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#'; + $this->namespaces['rss1'] = 'http://purl.org/rss/1.0/'; + $this->namespaces['dc'] = 'http://purl.org/dc/elements/1.1/'; + $this->namespaces['sy'] = 'http://purl.org/rss/1.0/modules/syndication/'; + + //Tag names to encode in CDATA + $this->CDATAEncoding = array('description', 'content:encoded', 'summary'); + } + + // Start # public functions --------------------------------------------- + + /** + * Adds a channel element indicating the program used to generate the feed. + * + * @return void + */ + public function addGenerator() + { + if ($this->version == Feed::ATOM) + $this->setChannelElement('atom:generator', 'FeedWriter', array('uri' => 'https://github.com/mibe/FeedWriter')); + else if ($this->version == Feed::RSS2) + $this->setChannelElement('generator', 'FeedWriter'); + else + die('The generator element is not supported in RSS1 feeds.'); + + return $this; + } + + /** + * Add a XML namespace to the internal list of namespaces. After that, + * custom channel elements can be used properly to generate a valid feed. + * + * @access public + * @param string namespace prefix + * @param string namespace name (URI) + * @return void + * @link http://www.w3.org/TR/REC-xml-names/ + */ + public function addNamespace($prefix, $uri) + { + $this->namespaces[$prefix] = $uri; + return $this; + } + + /** + * Add a channel element to the feed. + * + * @access public + * @param string name of the channel tag + * @param string content of the channel tag + * @param bool TRUE if this element can appear multiple times + * @return void + */ + public function setChannelElement($elementName, $content, $multiple = false) + { + if ($multiple === TRUE) + $this->channels[$elementName][] = $content; + else + $this->channels[$elementName] = $content; + + return $this; + } + + /** + * Set multiple channel elements from an array. Array elements + * should be 'channelName' => 'channelContent' format. + * + * @access public + * @param array array of channels + * @return void + */ + public function setChannelElementsFromArray($elementArray) + { + if (!is_array($elementArray)) + return; + + foreach ($elementArray as $elementName => $content) + { + $this->setChannelElement($elementName, $content); + } + + return $this; + } + + /** + * Get the appropriate MIME type string for the current feed. + * + * @access public + * @return string + */ + public function getMIMEType() + { + switch($this->version) + { + case Feed::RSS2 : $mimeType = "application/rss+xml"; + break; + case Feed::RSS1 : $mimeType = "application/rdf+xml"; + break; + case Feed::ATOM : $mimeType = "application/atom+xml"; + break; + default : $mimeType = "text/xml"; + } + + return $mimeType; + } + + /** + * Print the actual RSS/ATOM file + * + * Sets a Content-Type header and echoes the contents of the feed. + * Should only be used in situations where direct output is desired; + * if you need to pass a string around, use generateFeed() instead. + * + * @access public + * @param bool FALSE if the specific feed media type should be sent. + * @return void + */ + public function printFeed($useGenericContentType = false) + { + $contentType = "text/xml"; + + if (!$useGenericContentType) + { + $contentType = $this->getMIMEType(); + } + + header("Content-Type: " . $contentType); + echo $this->generateFeed(); + } + + /** + * Generate the feed. + * + * @access public + * @return string + */ + public function generateFeed() + { + return $this->makeHeader() + . $this->makeChannels() + . $this->makeItems() + . $this->makeFooter(); + } + + /** + * Create a new Item. + * + * @access public + * @return object instance of Item class + */ + public function createNewItem() + { + $Item = new Item($this->version); + return $Item; + } + + /** + * Add a FeedItem to the main class + * + * @access public + * @param Item instance of Item class + * @return void + */ + public function addItem(Item $feedItem) + { + if ($feedItem->getVersion() != $this->version) + die('Feed type mismatch: This instance can handle ' . $this->version . ' feeds only, but item with type ' . $feedItem->getVersion() . ' given.'); + + $this->items[] = $feedItem; + + return $this; + } + + + // Wrapper functions ------------------------------------------------------------------- + + /** Set the 'encoding' feed attribute + * @access public + * @param string value of 'encoding' feed attribute + * @return void + */ + public function setEncoding($encoding) + { + $this->encoding = $encoding; + return $this; + } + + /** + * Set the 'title' channel element + * + * @access public + * @param string value of 'title' channel tag + * @return void + */ + public function setTitle($title) + { + return $this->setChannelElement('title', $title); + } + + /** + * Set the date when the ATOM feed was lastly updated. + * + * This adds the 'updated' element to the feed. The value of the date parameter + * can be either an instance of the DateTime class, an integer containing a UNIX + * timestamp or a string which is parseable by PHP's 'strtotime' function. + * + * Not supported in RSS1 feeds. + * + * @access public + * @param DateTime|int|string Date which should be used. + * @return void + */ + public function setDate($date) + { + if ($this->version == Feed::RSS1) + die('The publication date is not supported in RSS1 feeds.'); + + // The feeds have different date formats. + $format = $this->version == Feed::ATOM ? \DATE_ATOM : \DATE_RSS; + + if ($date instanceof DateTime) + $date = $date->format($format); + else if(is_numeric($date) && $date >= 0) + $date = date($format, $date); + else if (is_string($date)) + $date = date($format, strtotime($date)); + else + die('The given date was not an instance of DateTime, a UNIX timestamp or a date string.'); + + if ($this->version == Feed::ATOM) + $this->setChannelElement('updated', $date); + else + $this->setChannelElement('lastBuildDate', $date); + + return $this; + } + + /** + * Set the 'description' channel element + * + * @access public + * @param string value of 'description' channel tag + * @return void + */ + public function setDescription($description) + { + if ($this->version != Feed::ATOM) + $this->setChannelElement('description', $description); + return $this; + } + + /** + * Set the 'link' channel element + * + * @access public + * @param string value of 'link' channel tag + * @return void + */ + public function setLink($link) + { + return $this->setChannelElement('link', $link); + } + + /** + * Set custom 'link' channel elements. + * + * In ATOM feeds, only one link with alternate relation and the same combination of + * type and hreflang values. + * + * @access public + * @param string URI of this link + * @param string relation type of the resource + * @param string media type of the target resource + * @param string language of the resource + * @param string human-readable information about the resource + * @param int length of the resource in bytes + * @link https://www.iana.org/assignments/link-relations/link-relations.xml + * @link https://tools.ietf.org/html/rfc4287#section-4.1.1 + */ + public function setAtomLink($href, $rel = null, $type = null, $hreflang = null, $title = null, $length = null) + { + $data = array('href' => $href); + + if ($rel != null) + { + if (!is_string($rel) || empty($rel)) + die('rel parameter must be a string and a valid relation identifier.'); + + $data['rel'] = $rel; + } + if ($type != null) + { + // Regex used from RFC 4287, page 41 + if (!is_string($type) || preg_match('/.+\/.+/', $type) != 1) + die('type parameter must be a string and a MIME type.'); + + $data['type'] = $type; + } + if ($hreflang != null) + { + // Regex used from RFC 4287, page 41 + if (!is_string($hreflang) || preg_match('/[A-Za-z]{1,8}(-[A-Za-z0-9]{1,8})*/', $hreflang) != 1) + die('hreflang parameter must be a string and a valid language code.'); + + $data['hreflang'] = $hreflang; + } + if ($title != null) + { + if (!is_string($title) || empty($title)) + die('title parameter must be a string and not empty.'); + + $data['title'] = $title; + } + if ($length != null) + { + if (!is_int($length) || $length < 0) + die('length parameter must be a positive integer.'); + + $data['length'] = (string)$length; + } + + // ATOM spec. has some restrictions on atom:link usage + // See RFC 4287, page 12 (4.1.1) + if ($this->version == Feed::ATOM) + { + foreach($this->channels as $key => $value) + { + if ($key != 'atom:link') + continue; + + // $value is an array , so check every element + foreach($value as $linkItem) + { + // Only one link with relation alternate and same hreflang & type is allowed. + if (@$linkItem['rel'] == 'alternate' && @$linkItem['hreflang'] == $hreflang && @$linkItem['type'] == $type) + die('The feed must not contain more than one link element with a relation of "alternate"' + . ' that has the same combination of type and hreflang attribute values.'); + } + } + } + + return $this->setChannelElement('atom:link', $data, true); + } + + /** + * Set an 'atom:link' channel element with relation=self attribute. + * Needs the full URL to this feed. + * + * @link http://www.rssboard.org/rss-profile#namespace-elements-atom-link + * @access public + * @param string URL to this feed + * @return void + */ + public function setSelfLink($url) + { + return $this->setAtomLink($url, 'self', $this->getMIMEType()); + } + + /** + * Set the 'image' channel element + * + * @access public + * @param string title of image + * @param string link url of the image + * @param string path url of the image + * @return void + */ + public function setImage($title, $link, $url) + { + return $this->setChannelElement('image', array('title'=>$title, 'link'=>$link, 'url'=>$url)); + } + + /** + * Set the 'about' channel element. Only for RSS 1.0 + * + * @access public + * @param string value of 'about' channel tag + * @return void + */ + public function setChannelAbout($url) + { + $this->data['ChannelAbout'] = $url; + return $this; + } + + /** + * Generate an UUID. + * + * The UUID is based on an MD5 hash. If no key is given, a unique ID as the input + * for the MD5 hash is generated. + * + * @author Anis uddin Ahmad + * @param string optional key on which the UUID is generated + * @param string an optional prefix + * @return string the formated UUID + */ + public static function uuid($key = null, $prefix = '') + { + $key = ($key == null) ? uniqid(rand()) : $key; + $chars = md5($key); + $uuid = substr($chars,0,8) . '-'; + $uuid .= substr($chars,8,4) . '-'; + $uuid .= substr($chars,12,4) . '-'; + $uuid .= substr($chars,16,4) . '-'; + $uuid .= substr($chars,20,12); + + return $prefix . $uuid; + } + // End # public functions ---------------------------------------------- + + // Start # private functions ---------------------------------------------- + + /** + * Returns all used XML namespace prefixes in this instance. + * This includes all channel elements and feed items. + * Unfortunately some namespace prefixes are not included, + * because they are hardcoded, e.g. rdf. + * + * @access private + * @return array Array with namespace prefix as value. + */ + private function getNamespacePrefixes() + { + $prefixes = array(); + + // Get all tag names from channel elements... + $tags = array_keys($this->channels); + + // ... and now all names from feed items + foreach ($this->items as $item) + $tags = array_merge($tags, array_keys($item->getElements())); + + // Look for prefixes in those tag names + foreach($tags as $tag) + { + $elements = explode(':', $tag); + + if (count($elements) != 2) + continue; + + $prefixes[] = $elements[0]; + } + + return array_unique($prefixes); + } + + /** + * Returns the XML header and root element, depending on the feed type. + * + * @access private + * @return void + */ + private function makeHeader() + { + $out = 'encoding.'" ?>' . PHP_EOL; + + $prefixes = $this->getNamespacePrefixes(); + $attributes = array(); + $tagName = ''; + $defaultNamespace = ''; + + if($this->version == Feed::RSS2) + { + $tagName = 'rss'; + $attributes['version'] = '2.0'; + } + elseif($this->version == Feed::RSS1) + { + $tagName = 'rdf:RDF'; + $prefixes[] = 'rdf'; + $defaultNamespace = $this->namespaces['rss1']; + } + else if($this->version == Feed::ATOM) + { + $tagName = 'feed'; + $defaultNamespace = $this->namespaces['atom']; + + // Ugly hack to remove the 'atom' value from the prefixes array. + $prefixes = array_flip($prefixes); + unset($prefixes['atom']); + $prefixes = array_flip($prefixes); + } + + // Iterate through every namespace prefix and add it to the element attributes. + foreach($prefixes as $prefix) + { + if (!isset($this->namespaces[$prefix])) + die('Unknown XML namespace prefix: \'' . $prefix . '\'. Use the addNamespace method to add support for this prefix.'); + else + $attributes['xmlns:' . $prefix] = $this->namespaces[$prefix]; + } + + // Include default namepsace, if required + if (!empty($defaultNamespace)) + $attributes['xmlns'] = $defaultNamespace; + + $out .= $this->makeNode($tagName, '', $attributes, true); + + return $out; + } + + /** + * Closes the open tags at the end of file + * + * @access private + * @return void + */ + private function makeFooter() + { + if($this->version == Feed::RSS2) + { + return '' . PHP_EOL . ''; + } + elseif($this->version == Feed::RSS1) + { + return ''; + } + else if($this->version == Feed::ATOM) + { + return ''; + } + } + + /** + * Creates a single node in XML format + * + * @access private + * @param string name of the tag + * @param mixed tag value as string or array of nested tags in 'tagName' => 'tagValue' format + * @param array Attributes (if any) in 'attrName' => 'attrValue' format + * @param string True if the end tag should be omitted. Defaults to false. + * @return string formatted xml tag + */ + private function makeNode($tagName, $tagContent, $attributes = null, $omitEndTag = false) + { + $nodeText = ''; + $attrText = ''; + + if(is_array($attributes) && count($attributes) > 0) + { + foreach ($attributes as $key => $value) + { + $value = htmlspecialchars($value); + $attrText .= " $key=\"$value\""; + } + } + + if(is_array($tagContent) && $this->version == Feed::RSS1) + { + $attrText = ' rdf:parseType="Resource"'; + } + + $attrText .= (in_array($tagName, $this->CDATAEncoding) && $this->version == Feed::ATOM) ? ' type="html"' : ''; + $nodeText .= "<{$tagName}{$attrText}>"; + $nodeText .= (in_array($tagName, $this->CDATAEncoding)) ? ' $value) + { + $nodeText .= $this->makeNode($key, $value); + } + } + else + { + $nodeText .= (in_array($tagName, $this->CDATAEncoding)) ? $this->sanitizeCDATA($tagContent) : htmlspecialchars($tagContent); + } + + $nodeText .= (in_array($tagName, $this->CDATAEncoding)) ? ']]>' : ''; + + if (!$omitEndTag) + $nodeText .= ""; + + $nodeText .= PHP_EOL; + + return $nodeText; + } + + /** + * Make the channels. + * + * @access private + * @return void + */ + private function makeChannels() + { + $out = ''; + + //Start channel tag + switch ($this->version) + { + case Feed::RSS2: + $out .= '' . PHP_EOL; + break; + case Feed::RSS1: + $out .= (isset($this->data['ChannelAbout']))? "data['ChannelAbout']}\">" : "channels['link']}\">"; + break; + } + + //Print Items of channel + foreach ($this->channels as $key => $value) + { + // ATOM feed needs some special handling + if ($this->version == Feed::ATOM) + { + // Strip all ATOM namespace prefixes from tags. Not needed here, because the ATOM namespace name is + // used as default namespace. + if (strncmp($key, 'atom', 4) == 0) + $key = substr($key, 5); + + if ($key == 'link') + { + if (is_array($value)) + { + // $value is an array containing multiple atom:link element attributes + foreach($value as $attributes) + { + // $attributes contains actually the node attributes, not the value. + $out .= $this->makeNode($key, '', $attributes); + } + } + else + { + // ATOM prints link element as href attribute + $out .= $this->makeNode($key, '', array('href' => $value)); + //Add the id for ATOM + $out .= $this->makeNode('id', Feed::uuid($value, 'urn:uuid:')); + } + } + else + $out .= $this->makeNode($key, $value); + } + else + { + if ($key == 'atom:link') + { + // $value is an array containing multiple atom:link element attributes + foreach($value as $attributes) + { + // $attributes contains actually the node attributes, not the value. + $out .= $this->makeNode($key, '', $attributes); + } + } + else + { + $out .= $this->makeNode($key, $value); + } + } + + } + + //RSS 1.0 have special tag with channel + if($this->version == Feed::RSS1) + { + $out .= "" . PHP_EOL . "" . PHP_EOL; + foreach ($this->items as $item) + { + $thisItems = $item->getElements(); + $out .= "" . PHP_EOL; + } + $out .= "" . PHP_EOL . "" . PHP_EOL . "" . PHP_EOL; + } + + return $out; + } + + /** + * Prints formatted feed items + * + * @access private + * @return void + */ + private function makeItems() + { + $out = ''; + + foreach ($this->items as $item) + { + $thisItems = $item->getElements(); + + // the argument is printed as rdf:about attribute of item in rss 1.0 + $out .= $this->startItem($thisItems['link']['content']); + + foreach ($thisItems as $feedItem) + { + $name = $feedItem['name']; + + // Strip all ATOM namespace prefixes from tags when feed is an ATOM feed. + // Not needed here, because the ATOM namespace name is used as default namespace. + if ($this->version == Feed::ATOM && strncmp($name, 'atom', 4) == 0) + $name = substr($name, 5); + + $out .= $this->makeNode($name, $feedItem['content'], $feedItem['attributes']); + } + $out .= $this->endItem(); + } + + return $out; + } + + /** + * Make the starting tag of channels + * + * @access private + * @param string The vale of about tag which is used for RSS 1.0 only. + * @return void + */ + private function startItem($about = false) + { + $out = ''; + + if($this->version == Feed::RSS2) + { + $out .= '' . PHP_EOL; + } + else if($this->version == Feed::RSS1) + { + if($about) + { + $out .= "" . PHP_EOL; + } + else + { + throw new Exception("link element is not set - It's required for RSS 1.0 to be used as the about attribute of the item tag."); + } + } + else if($this->version == Feed::ATOM) + { + $out .= "" . PHP_EOL; + } + + return $out; + } + + /** + * Closes feed item tag + * + * @access private + * @return void + */ + private function endItem() + { + if($this->version == Feed::RSS2 || $this->version == Feed::RSS1) + { + return '' . PHP_EOL; + } + else if($this->version == Feed::ATOM) + { + return '' . PHP_EOL; + } + } + + /** + * Sanitizes data which will be later on returned as CDATA in the feed. + * + * A "]]>" respectively "", "]]>", $text); + $text = str_replace(" + * Copyright (C) 2010-2013 Michael Bemmerl + * + * This file is part of the "Universal Feed Writer" project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/** + * Universal Feed Writer + * + * Item class - Used as feed element in Feed class + * + * @package UniversalFeedWriter + * @author Anis uddin Ahmad + * @link http://www.ajaxray.com/projects/rss + */ +class Item +{ + /** + * Collection of feed item elements + */ + private $elements = array(); + + /** + * Contains the format of this feed. + */ + private $version; + + /** + * Is used as a suffix when multiple elements have the same name. + **/ + private $_cpt = 0; + + /** + * Constructor + * + * @param constant (RSS1/RSS2/ATOM) RSS2 is default. + */ + function __construct($version = Feed::RSS2) + { + $this->version = $version; + } + + /** + * Return an unique number + * @access private + * @return int + **/ + private function cpt() { + return $this->_cpt++; + } + + /** + * Add an element to elements array + * + * @access public + * @param string The tag name of an element + * @param string The content of tag + * @param array Attributes(if any) in 'attrName' => 'attrValue' format + * @param boolean Specifies if an already existing element is overwritten. + * @param boolean Specifies if multiple elements of the same name are allowed. + * @return void + */ + public function addElement($elementName, $content, $attributes = null, $overwrite = FALSE, $allowMultiple = FALSE) + { + + $key = $elementName; + + // return if element already exists & if overwriting is disabled + // & if multiple elements are not allowed. + if (isset($this->elements[$elementName]) && !$overwrite) { + if (!$allowMultiple) + return; + + $key .= '-' . $this->cpt(); + } + + + $this->elements[$key]['name'] = $elementName; + $this->elements[$key]['content'] = $content; + $this->elements[$key]['attributes'] = $attributes; + + return $this; + } + + /** + * Set multiple feed elements from an array. + * Elements which have attributes cannot be added by this method + * + * @access public + * @param array array of elements in 'tagName' => 'tagContent' format. + * @return void + */ + public function addElementArray($elementArray) + { + if (!is_array($elementArray)) + return; + + foreach ($elementArray as $elementName => $content) + { + $this->addElement($elementName, $content); + } + + return $this; + } + + /** + * Return the collection of elements in this feed item + * + * @access public + * @return array + */ + public function getElements() + { + return $this->elements; + } + + /** + * Return the type of this feed item + * + * @access public + * @return string The feed type, as defined in Feed.php + */ + public function getVersion() + { + return $this->version; + } + + // Wrapper functions ------------------------------------------------------ + + /** + * Set the 'description' element of feed item + * + * @access public + * @param string The content of 'description' or 'summary' element + * @return void + */ + public function setDescription($description) + { + $tag = ($this->version == Feed::ATOM) ? 'summary' : 'description'; + return $this->addElement($tag, $description); + } + + /** + * Set the 'content' element of the feed item + * For ATOM feeds only + * + * @access public + * @param string Content for the item (i.e., the body of a blog post). + * @return void + */ + public function setContent($content) + { + if ($this->version != Feed::ATOM) + die('The content element is supported in ATOM feeds only.'); + + return $this->addElement('content', $content, array('type' => 'html')); + } + + + /** + * Set the 'title' element of feed item + * + * @access public + * @param string The content of 'title' element + * @return void + */ + public function setTitle($title) + { + return $this->addElement('title', $title); + } + + /** + * Set the 'date' element of the feed item. + * + * The value of the date parameter can be either an instance of the + * DateTime class, an integer containing a UNIX timestamp or a string + * which is parseable by PHP's 'strtotime' function. + * + * @access public + * @param DateTime|int|string Date which should be used. + * @return void + */ + public function setDate($date) + { + if(!is_numeric($date)) + { + if ($date instanceof DateTime) + $date = $date->getTimestamp(); + else + { + $date = strtotime($date); + + if ($date === FALSE) + die('The given date string was not parseable.'); + } + } + else if ($date < 0) + die('The given date is not an UNIX timestamp.'); + + if($this->version == Feed::ATOM) + { + $tag = 'updated'; + $value = date(\DATE_ATOM, $date); + } + elseif($this->version == Feed::RSS2) + { + $tag = 'pubDate'; + $value = date(\DATE_RSS, $date); + } + else + { + $tag = 'dc:date'; + $value = date("Y-m-d", $date); + } + + return $this->addElement($tag, $value); + } + + /** + * Set the 'link' element of feed item + * + * @access public + * @param string The content of 'link' element + * @return void + */ + public function setLink($link) + { + if($this->version == Feed::RSS2 || $this->version == Feed::RSS1) + { + $this->addElement('link', $link); + } + else + { + $this->addElement('link','',array('href'=>$link)); + $this->addElement('id', Feed::uuid($link,'urn:uuid:')); + } + + return $this; + } + + /** + * Attach a external media to the feed item. + * Not supported in RSS 1.0 feeds. + * + * See RFC 4288 for syntactical correct MIME types. + * + * Note that you should avoid the use of more than one enclosure in one item, + * since some RSS aggregators don't support it. + * + * @access public + * @param string The URL of the media. + * @param integer The length of the media. + * @param string The MIME type attribute of the media. + * @param boolean Specifies, if multiple enclosures are allowed + * @return void + * @link https://tools.ietf.org/html/rfc4288 + */ + public function addEnclosure($url, $length, $type, $multiple = TRUE) + { + if ($this->version == Feed::RSS1) + die('Media attachment is not supported in RSS1 feeds.'); + + // the length parameter should be set to 0 if it can't be determined + // see http://www.rssboard.org/rss-profile#element-channel-item-enclosure + if (!is_numeric($length) || $length < 0) + die('The length parameter must be an integer and greater or equals to zero.'); + + // Regex used from RFC 4287, page 41 + if (!is_string($type) || preg_match('/.+\/.+/', $type) != 1) + die('type parameter must be a string and a MIME type.'); + + $attributes = array('length' => $length, 'type' => $type); + + if ($this->version == Feed::RSS2) + { + $attributes['url'] = $url; + $this->addElement('enclosure', '', $attributes, FALSE, $multiple); + } + else + { + $attributes['href'] = $url; + $attributes['rel'] = 'enclosure'; + $this->addElement('atom:link', '', $attributes, FALSE, $multiple); + } + + return $this; + } + + /** + * Alias of addEnclosure, for backward compatibility. Using only this + * method ensure that the 'enclosure' element will be present only once. + * + * @access public + * @param string The URL of the media. + * @param integer The length of the media. + * @param string The MIME type attribute of the media. + * @return void + * @link https://tools.ietf.org/html/rfc4288 + * + **/ + public function setEnclosure($url, $length, $type) { + return $this->addEnclosure($url, $length, $type, false); + } + + /** + * Set the 'author' element of feed item. + * Not supported in RSS 1.0 feeds. + * + * @access public + * @param string The author of this item + * @param string Optional email address of the author + * @param string Optional URI related to the author + * @return void + */ + public function setAuthor($author, $email = null, $uri = null) + { + switch($this->version) + { + case Feed::RSS1: die('The author element is not supported in RSS1 feeds.'); + break; + case Feed::RSS2: + if ($email != null) + $author = $email . ' (' . $author . ')'; + + $this->addElement('author', $author); + break; + case Feed::ATOM: + $elements = array('name' => $author); + + // Regex from RFC 4287 page 41 + if ($email != null && preg_match('/.+@.+/', $email) == 1) + $elements['email'] = $email; + + if ($uri != null) + $elements['uri'] = $uri; + + $this->addElement('author', $elements); + break; + } + + return $this; + } + + /** + * Set the unique identifier of the feed item + * + * @access public + * @param string The unique identifier of this item + * @param boolean The value of the 'isPermaLink' attribute in RSS 2 feeds. + * @return void + */ + public function setId($id, $permaLink = false) + { + if ($this->version == Feed::RSS2) + { + if (!is_bool($permaLink)) + die('The permaLink parameter must be boolean.'); + + $permaLink = $permaLink ? 'true' : 'false'; + + $this->addElement('guid', $id, array('isPermaLink' => $permaLink)); + } + else if ($this->version == Feed::ATOM) + { + $this->addElement('id', Feed::uuid($id,'urn:uuid:'), NULL, TRUE); + } + else + die('A unique ID is not supported in RSS1 feeds.'); + + return $this; + } + + } // end of class Item diff --git a/sources/lib/FeedWriter/README b/sources/lib/FeedWriter/README new file mode 100644 index 0000000..621497f --- /dev/null +++ b/sources/lib/FeedWriter/README @@ -0,0 +1,41 @@ +This package can be used to generate feeds in either RSS 1.0, RSS 2.0 or ATOM +formats. + +There are three main classes that abstract the feed information and another to +encapsulate the feed items information. + +Applications can create feed writer object, several feed item objects, set +several types of properties of either feeds and feed items, and add items to +the feed. + +Once a feed is fully composed with its items, the feed writer class can generate +the necessary XML structure to describe the feed in the RSS or ATOM formats. +The feed is generated as part of the current feed output. + + +Requirements +============ + +PHP >= 5.3 + +If you don't have 5.3 available on your system, there's a version supporting +PHP >= 5.0 in the "legacy-php-5.0" branch. + + +Documentation +============= + +The documentation can be found in the "gh-pages" branch or on GitHub Pages: +http://mibe.github.io/FeedWriter/ + + +Authors +======= +(in chronological order) + +Anis uddin Ahmad +Michael Bemmerl +Phil Freo +Paul Ferrett +Brennen Bearnes +Michael Robinson diff --git a/sources/lib/FeedWriter/RSS1.php b/sources/lib/FeedWriter/RSS1.php new file mode 100644 index 0000000..5ceca0d --- /dev/null +++ b/sources/lib/FeedWriter/RSS1.php @@ -0,0 +1,37 @@ + + * + * This file is part of the "Universal Feed Writer" project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/** + * Wrapper for creating RSS1 feeds + * + * @package UniversalFeedWriter + */ +class RSS1 extends Feed +{ + /** + * {@inheritdoc} + */ + function __construct() + { + parent::__construct(Feed::RSS1); + } +} diff --git a/sources/lib/FeedWriter/RSS2.php b/sources/lib/FeedWriter/RSS2.php new file mode 100644 index 0000000..ce5b5f7 --- /dev/null +++ b/sources/lib/FeedWriter/RSS2.php @@ -0,0 +1,37 @@ + + * + * This file is part of the "Universal Feed Writer" project. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/** + * Wrapper for creating RSS2 feeds + * + * @package UniversalFeedWriter + */ +class RSS2 extends Feed +{ + /** + * {@inheritdoc} + */ + function __construct() + { + parent::__construct(Feed::RSS2); + } +} diff --git a/sources/lib/FeedWriter/composer.json b/sources/lib/FeedWriter/composer.json new file mode 100644 index 0000000..428365c --- /dev/null +++ b/sources/lib/FeedWriter/composer.json @@ -0,0 +1,41 @@ +{ + "name": "mibe/feedwriter", + "description": "Generate feeds in either RSS 1.0, RSS 2.0 or ATOM formats", + "keywords": ["RSS"], + "homepage": "https://github.com/mibe/FeedWriter", + "type": "library", + "license": "GPL-3.0", + "authors": [ + { + "name": "Anis uddin Ahmad" + }, + { + "name": "Michael Bemmerl", + "email": "mail@mx-server.de" + }, + { + "name": "Phil Freo" + }, + { + "name": "Paul Ferrett" + }, + { + "name": "Brennen Bearnes" + }, + { + "name": "Michael Robinson", + "email": "mike@pagesofinterest.net" + } + ], + "minimum-stability": "dev", + "autoload": { + "classmap": [ + "ATOM.php", + "Feed.php", + "Item.php", + "RSS1.php", + "RSS2.php" + ] + } +} + diff --git a/sources/lib/Http/Client/Abstract.php b/sources/lib/Http/Client/Abstract.php new file mode 100644 index 0000000..3781489 --- /dev/null +++ b/sources/lib/Http/Client/Abstract.php @@ -0,0 +1,216 @@ +_proxy_type = $type; + return $this; + } + + public function getProxyType() + { + return $this->_proxy_type; + } + + /** + * @param string $ip + * @return HttpClientAbstract + */ + public function setProxyIp($ip) + { + if (0 === strpos($ip, "http://") || 0 === strpos($ip, "https://")) { + $this->setProxyType(self::PROXY_TYPE_WEB); + } else { + $this->setProxyType(self::PROXY_TYPE_HTTP); + } + $this->_proxy_ip = $ip; + return $this; + } + + public function getProxyIp() + { + return $this->_proxy_ip; + } + + /** + * @param int $proxy_port + * @return HttpClientAbstract + */ + public function setProxyPort($proxy_port) + { + $this->_proxy_port = $proxy_port; + return $this; + } + + /** + * @return int + */ + public function getProxyPort() + { + return $this->_proxy_port; + } + + /** + * @param string $proxy_user + * @return HttpClientAbstract + */ + public function setProxyUser($proxy_user) + { + $this->_proxy_user = $proxy_user; + return $this; + } + + /** + * @return string + */ + public function getProxyUser() + { + return $this->_proxy_user; + } + + /** + * @return boolean + */ + public function hasProxy() + { + return !empty($this->_proxy_ip) && !empty($this->_proxy_port); + } + + /** + * @param string $proxy_password + * @return HttpClientAbstract + */ + public function setProxyPassword($proxy_password) + { + $this->_proxy_password = $proxy_password; + return $this; + } + + /** + * @return string + */ + public function getProxyPassword() + { + return $this->_proxy_password; + } + + /** + * @param string $user_agent + * @return HttpClientAbstract + */ + public function setUserAgent($user_agent) + { + $this->_user_agent = $user_agent; + return $this; + } + + /** + * @return string + */ + public function getUserAgent() + { + return $this->_user_agent; + } + + /** + * @param string $method + * @throws Exception + * @return HttpClientAbstract + */ + public function setMethod($method) + { + if ($method != self::METHOD_GET && $method != self::METHOD_POST) { + throw new Exception("Méthode invalide."); + } + $this->_method = $method; + return $this; + } + + /** + * @return string + */ + public function getMethod() + { + return $this->_method; + } + + /** + * Définit l'URL à appeler. + * @param string $url + * @return HttpClientAbstract + */ + public function setUrl($url) + { + $this->_url = $url; + return $this; + } + + /** + * @return string + */ + public function getUrl() + { + return $this->_url; + } + + /** + * @param bool $download_body + * @return HttpClientAbstract + */ + public function setDownloadBody($download_body) + { + $this->_download_body = (bool)$download_body; + return $this; + } + + /** + * @return string + */ + public function getBody() + { + return $this->_body; + } + + /** + * @return int + */ + public function getRespondCode() + { + return $this->_respond_code; + } + + /** + * Retourne la dernière erreur générée. + * @return string + */ + abstract function getError(); + + abstract function request(); +} \ No newline at end of file diff --git a/sources/lib/Http/Client/Curl.php b/sources/lib/Http/Client/Curl.php new file mode 100644 index 0000000..16b7e80 --- /dev/null +++ b/sources/lib/Http/Client/Curl.php @@ -0,0 +1,95 @@ +_resource = curl_init(); + curl_setopt($this->_resource, CURLOPT_HEADER, false); + curl_setopt($this->_resource, CURLOPT_RETURNTRANSFER, true); + if (!ini_get("safe_mode") && !ini_get("open_basedir")) { + curl_setopt($this->_resource, CURLOPT_FOLLOWLOCATION, true); + } + curl_setopt($this->_resource, CURLOPT_CONNECTTIMEOUT, 5); + } + + public function request($url = null) + { + if (!$this->_url && !$url) { + throw new Exception("Aucune URL à appeler."); + } + if ($url) { + $this->setUrl($url); + } + if (!isset($this->_method) || $this->_method == self::METHOD_GET) { + curl_setopt($this->_resource, CURLOPT_HTTPGET, true); + } else { + curl_setopt($this->_resource, CURLOPT_POST, true); + } + if ($this->_proxy_ip) { + if ($this->getProxyType() == self::PROXY_TYPE_WEB) { + $url = $this->_proxy_ip.urlencode($this->getUrl()); + } else { + curl_setopt($this->_resource, CURLOPT_PROXY, $this->_proxy_ip); + if (!$this->_proxy_type || $this->_proxy_type == self::PROXY_TYPE_HTTP) { + curl_setopt($this->_resource, CURLOPT_PROXYTYPE, CURLPROXY_HTTP); + } else { + curl_setopt($this->_resource, CURLOPT_PROXYTYPE, CURLPROXY_SOCKS5); + } + if ($this->_proxy_port) { + curl_setopt($this->_resource, CURLOPT_PROXYPORT, $this->_proxy_port); + } + if ($this->_proxy_user) { + curl_setopt($this->_resource, CURLOPT_PROXYUSERPWD, $this->_proxy_user.":".$this->_proxy_password); + } + } + } + if ($userAgent = $this->getUserAgent()) { + curl_setopt($this->_resource, CURLOPT_USERAGENT, $userAgent); + } + curl_setopt($this->_resource, CURLOPT_NOBODY, !$this->_download_body); + curl_setopt($this->_resource, CURLOPT_URL, $url); + $body = curl_exec($this->_resource); + $this->_respond_code = curl_getinfo($this->_resource, CURLINFO_HTTP_CODE); + + if ($this->_respond_code == 301 || $this->_respond_code == 302) { + $redirect = curl_getinfo($this->_resource, CURLINFO_REDIRECT_URL); + if ($redirect) { + return $this->request($redirect); + } + } + + if ($this->getProxyType() == self::PROXY_TYPE_WEB) { + // restaure les vrai URL + if (preg_match_all("#<(?:img|a)[^>]+(?:src|href)=(?:'|\")(https?://.*(http[^'\"]+))(?:'|\")[^>]*/?>#ismU", $body, $matches, PREG_SET_ORDER)) { + $replaceFrom = array(); + $replaceTo = array(); + foreach ($matches AS $m) { + $replaceFrom[] = $m[1]; + $replaceTo[] = urldecode($m[2]); + } + $body = str_replace($replaceFrom, $replaceTo, $body); + } + } + $this->_body = $body; + return $this->_body; + } + + /** + * Retourne la dernière erreur générée par cURL. + * @return string + */ + public function getError() + { + return curl_error($this->_resource); + } + + public function __destruct() + { + curl_close($this->_resource); + } +} diff --git a/sources/lib/Lbc/CategoryCollection.php b/sources/lib/Lbc/CategoryCollection.php new file mode 100644 index 0000000..1493e11 --- /dev/null +++ b/sources/lib/Lbc/CategoryCollection.php @@ -0,0 +1,86 @@ + array( + 2 => "Voitures", + 3 => "Motos", + 4 => "Caravaning", + 5 => "Utilitaires", + 6 => "Equipement Auto", + 44 => "Equipement Moto", + 50 => "Equipement Caravaning", + 7 => "Nautisme", + 51 => "Equipement Nautisme" + ), + "Immobilier" => array( + 9 => "Ventes immobilières", + 10 => "Locations", + 11 => "Colocations", + 12 => "Locations de vacances", + 13 => "Bureaux & Commerces" + ), + "Multimedia" => array( + 15 => "Informatique", + 43 => "Consoles & Jeux vidéo", + 16 => "Image & Son", + 17 => "Téléphonie" + ), + "Maison" => array( + 19 => "Ameublement", + 20 => "Electroménager", + 45 => "Arts de la table", + 39 => "Décoration", + 46 => "Linge de maison", + 21 => "Bricolage", + 52 => "Jardinage", + 22 => "Vêtements", + 53 => "Chaussures", + 47 => "Accessoires & Bagagerie", + 42 => "Montres & Bijoux", + 23 => "Equipement bébé", + 54 => "Vêtements bébé" + ), + "Loisirs" => array( + 25 => "DVD / Films", + 26 => "CD / Musique", + 27 => "Livres", + 28 => "Animaux", + 55 => "Vélos", + 29 => "Sports & Hobbies", + 30 => "Instruments de musique", + 40 => "Collection", + 41 => "Jeux & Jouets", + 48 => "Vins & Gastronomie" + ), + "Matériel professionnel" => array( + 57 => "Matériel Agricole", + 58 => "Transport - Manutention", + 59 => "BTP - Chantier Gros-oeuvre", + 60 => "Outillage - Matériaux 2nd-oeuvre", + 32 => "Équipements Industriels", + 61 => "Restauration - Hôtellerie", + 62 => "Fournitures de Bureau", + 63 => "Commerces & Marchés", + 64 => "Matériel Médical" + ), + "Emploi & Services" => array( + 33 => "Emploi", + 34 => "Services", + 35 => "Billetterie", + 49 => "Evénements", + 36 => "Cours particuliers" + ), + "--" => array( + 38 => "Autres" + ) + ); + + public function fetchAll() + { + return $this->_categories; + } +} \ No newline at end of file diff --git a/sources/lib/Log4php/Logger.php b/sources/lib/Log4php/Logger.php new file mode 100644 index 0000000..1bc53bc --- /dev/null +++ b/sources/lib/Log4php/Logger.php @@ -0,0 +1,596 @@ + + *
  • {@link trace()}
  • + *
  • {@link debug()}
  • + *
  • {@link info()}
  • + *
  • {@link warn()}
  • + *
  • {@link error()}
  • + *
  • {@link fatal()}
  • + * + * + * @package log4php + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0 + * @version SVN: $Id: Logger.php 1395241 2012-10-07 08:28:53Z ihabunek $ + * @link http://logging.apache.org/log4php + */ +class Logger { + + /** + * Logger additivity. If set to true then child loggers will inherit + * the appenders of their ancestors by default. + * @var boolean + */ + private $additive = true; + + /** + * The Logger's fully qualified class name. + * TODO: Determine if this is useful. + */ + private $fqcn = 'Logger'; + + /** The assigned Logger level. */ + private $level; + + /** The name of this Logger instance. */ + private $name; + + /** The parent logger. Set to null if this is the root logger. */ + private $parent; + + /** A collection of appenders linked to this logger. */ + private $appenders = array(); + + /** + * Constructor. + * @param string $name Name of the logger. + */ + public function __construct($name) { + $this->name = $name; + } + + /** + * Returns the logger name. + * @return string + */ + public function getName() { + return $this->name; + } + + /** + * Returns the parent Logger. Can be null if this is the root logger. + * @return Logger + */ + public function getParent() { + return $this->parent; + } + + // ****************************************** + // *** Logging methods *** + // ****************************************** + + /** + * Log a message object with the TRACE level. + * + * @param mixed $message message + * @param Exception $throwable Optional throwable information to include + * in the logging event. + */ + public function trace($message, $throwable = null) { + $this->log(LoggerLevel::getLevelTrace(), $message, $throwable); + } + + /** + * Log a message object with the DEBUG level. + * + * @param mixed $message message + * @param Exception $throwable Optional throwable information to include + * in the logging event. + */ + public function debug($message, $throwable = null) { + $this->log(LoggerLevel::getLevelDebug(), $message, $throwable); + } + + /** + * Log a message object with the INFO Level. + * + * @param mixed $message message + * @param Exception $throwable Optional throwable information to include + * in the logging event. + */ + public function info($message, $throwable = null) { + $this->log(LoggerLevel::getLevelInfo(), $message, $throwable); + } + + /** + * Log a message with the WARN level. + * + * @param mixed $message message + * @param Exception $throwable Optional throwable information to include + * in the logging event. + */ + public function warn($message, $throwable = null) { + $this->log(LoggerLevel::getLevelWarn(), $message, $throwable); + } + + /** + * Log a message object with the ERROR level. + * + * @param mixed $message message + * @param Exception $throwable Optional throwable information to include + * in the logging event. + */ + public function error($message, $throwable = null) { + $this->log(LoggerLevel::getLevelError(), $message, $throwable); + } + + /** + * Log a message object with the FATAL level. + * + * @param mixed $message message + * @param Exception $throwable Optional throwable information to include + * in the logging event. + */ + public function fatal($message, $throwable = null) { + $this->log(LoggerLevel::getLevelFatal(), $message, $throwable); + } + + /** + * Log a message using the provided logging level. + * + * @param LoggerLevel $level The logging level. + * @param mixed $message Message to log. + * @param Exception $throwable Optional throwable information to include + * in the logging event. + */ + public function log(LoggerLevel $level, $message, $throwable = null) { + if($this->isEnabledFor($level)) { + $event = new LoggerLoggingEvent($this->fqcn, $this, $level, $message, null, $throwable); + $this->callAppenders($event); + } + + // Forward the event upstream if additivity is turned on + if(isset($this->parent) && $this->getAdditivity()) { + + // Use the event if already created + if (isset($event)) { + $this->parent->logEvent($event); + } else { + $this->parent->log($level, $message, $throwable); + } + } + } + + /** + * Logs an already prepared logging event object. + * @param LoggerLoggingEvent $event + */ + public function logEvent(LoggerLoggingEvent $event) { + if($this->isEnabledFor($event->getLevel())) { + $this->callAppenders($event); + } + + // Forward the event upstream if additivity is turned on + if(isset($this->parent) && $this->getAdditivity()) { + $this->parent->logEvent($event); + } + } + + /** + * If assertion parameter evaluates as false, then logs the message + * using the ERROR level. + * + * @param bool $assertion + * @param string $msg message to log + */ + public function assertLog($assertion = true, $msg = '') { + if($assertion == false) { + $this->error($msg); + } + } + + /** + * This method creates a new logging event and logs the event without + * further checks. + * + * It should not be called directly. Use {@link trace()}, {@link debug()}, + * {@link info()}, {@link warn()}, {@link error()} and {@link fatal()} + * wrappers. + * + * @param string $fqcn Fully qualified class name of the Logger + * @param Exception $throwable Optional throwable information to include + * in the logging event. + * @param LoggerLevel $level log level + * @param mixed $message message to log + */ + public function forcedLog($fqcn, $throwable, LoggerLevel $level, $message) { + $event = new LoggerLoggingEvent($fqcn, $this, $level, $message, null, $throwable); + $this->callAppenders($event); + + // Forward the event upstream if additivity is turned on + if(isset($this->parent) && $this->getAdditivity()) { + $this->parent->logEvent($event); + } + } + + /** + * Forwards the given logging event to all linked appenders. + * @param LoggerLoggingEvent $event + */ + public function callAppenders($event) { + foreach($this->appenders as $appender) { + $appender->doAppend($event); + } + } + + // ****************************************** + // *** Checker methods *** + // ****************************************** + + /** + * Check whether this Logger is enabled for a given Level passed as parameter. + * + * @param LoggerLevel level + * @return boolean + */ + public function isEnabledFor(LoggerLevel $level) { + return $level->isGreaterOrEqual($this->getEffectiveLevel()); + } + + /** + * Check whether this Logger is enabled for the TRACE Level. + * @return boolean + */ + public function isTraceEnabled() { + return $this->isEnabledFor(LoggerLevel::getLevelTrace()); + } + + /** + * Check whether this Logger is enabled for the DEBUG Level. + * @return boolean + */ + public function isDebugEnabled() { + return $this->isEnabledFor(LoggerLevel::getLevelDebug()); + } + + /** + * Check whether this Logger is enabled for the INFO Level. + * @return boolean + */ + public function isInfoEnabled() { + return $this->isEnabledFor(LoggerLevel::getLevelInfo()); + } + + /** + * Check whether this Logger is enabled for the WARN Level. + * @return boolean + */ + public function isWarnEnabled() { + return $this->isEnabledFor(LoggerLevel::getLevelWarn()); + } + + /** + * Check whether this Logger is enabled for the ERROR Level. + * @return boolean + */ + public function isErrorEnabled() { + return $this->isEnabledFor(LoggerLevel::getLevelError()); + } + + /** + * Check whether this Logger is enabled for the FATAL Level. + * @return boolean + */ + public function isFatalEnabled() { + return $this->isEnabledFor(LoggerLevel::getLevelFatal()); + } + + // ****************************************** + // *** Configuration methods *** + // ****************************************** + + /** + * Adds a new appender to the Logger. + * @param LoggerAppender $appender The appender to add. + */ + public function addAppender($appender) { + $appenderName = $appender->getName(); + $this->appenders[$appenderName] = $appender; + } + + /** Removes all appenders from the Logger. */ + public function removeAllAppenders() { + foreach($this->appenders as $name => $appender) { + $this->removeAppender($name); + } + } + + /** + * Remove the appender passed as parameter form the Logger. + * @param mixed $appender an appender name or a {@link LoggerAppender} instance. + */ + public function removeAppender($appender) { + if($appender instanceof LoggerAppender) { + $appender->close(); + unset($this->appenders[$appender->getName()]); + } else if (is_string($appender) and isset($this->appenders[$appender])) { + $this->appenders[$appender]->close(); + unset($this->appenders[$appender]); + } + } + + /** + * Returns the appenders linked to this logger as an array. + * @return array collection of appender names + */ + public function getAllAppenders() { + return $this->appenders; + } + + /** + * Returns a linked appender by name. + * @return LoggerAppender + */ + public function getAppender($name) { + return $this->appenders[$name]; + } + + /** + * Sets the additivity flag. + * @param boolean $additive + */ + public function setAdditivity($additive) { + $this->additive = (bool)$additive; + } + + /** + * Returns the additivity flag. + * @return boolean + */ + public function getAdditivity() { + return $this->additive; + } + + /** + * Starting from this Logger, search the Logger hierarchy for a non-null level and return it. + * @see LoggerLevel + * @return LoggerLevel or null + */ + public function getEffectiveLevel() { + for($logger = $this; $logger !== null; $logger = $logger->getParent()) { + if($logger->getLevel() !== null) { + return $logger->getLevel(); + } + } + } + + /** + * Get the assigned Logger level. + * @return LoggerLevel The assigned level or null if none is assigned. + */ + public function getLevel() { + return $this->level; + } + + /** + * Set the Logger level. + * + * Use LoggerLevel::getLevelXXX() methods to get a LoggerLevel object, e.g. + * $logger->setLevel(LoggerLevel::getLevelInfo()); + * + * @param LoggerLevel $level The level to set, or NULL to clear the logger level. + */ + public function setLevel(LoggerLevel $level = null) { + $this->level = $level; + } + + /** + * Checks whether an appender is attached to this logger instance. + * + * @param LoggerAppender $appender + * @return boolean + */ + public function isAttached(LoggerAppender $appender) { + return isset($this->appenders[$appender->getName()]); + } + + /** + * Sets the parent logger. + * @param Logger $logger + */ + public function setParent(Logger $logger) { + $this->parent = $logger; + } + + // ****************************************** + // *** Static methods and properties *** + // ****************************************** + + /** The logger hierarchy used by log4php. */ + private static $hierarchy; + + /** Inidicates if log4php has been initialized */ + private static $initialized = false; + + /** + * Returns the hierarchy used by this Logger. + * + * Caution: do not use this hierarchy unless you have called initialize(). + * To get Loggers, use the Logger::getLogger and Logger::getRootLogger + * methods instead of operating on on the hierarchy directly. + * + * @return LoggerHierarchy + */ + public static function getHierarchy() { + if(!isset(self::$hierarchy)) { + self::$hierarchy = new LoggerHierarchy(new LoggerRoot()); + } + return self::$hierarchy; + } + + /** + * Returns a Logger by name. If it does not exist, it will be created. + * + * @param string $name The logger name + * @return Logger + */ + public static function getLogger($name) { + if(!self::isInitialized()) { + self::configure(); + } + return self::getHierarchy()->getLogger($name); + } + + /** + * Returns the Root Logger. + * @return LoggerRoot + */ + public static function getRootLogger() { + if(!self::isInitialized()) { + self::configure(); + } + return self::getHierarchy()->getRootLogger(); + } + + /** + * Clears all Logger definitions from the logger hierarchy. + * @return boolean + */ + public static function clear() { + return self::getHierarchy()->clear(); + } + + /** + * Destroy configurations for logger definitions + */ + public static function resetConfiguration() { + self::getHierarchy()->resetConfiguration(); + self::getHierarchy()->clear(); // TODO: clear or not? + self::$initialized = false; + } + + /** + * Safely close all appenders. + * @deprecated This is no longer necessary due the appenders shutdown via + * destructors. + */ + public static function shutdown() { + return self::getHierarchy()->shutdown(); + } + + /** + * check if a given logger exists. + * + * @param string $name logger name + * @return boolean + */ + public static function exists($name) { + return self::getHierarchy()->exists($name); + } + + /** + * Returns an array this whole Logger instances. + * @see Logger + * @return array + */ + public static function getCurrentLoggers() { + return self::getHierarchy()->getCurrentLoggers(); + } + + /** + * Configures log4php. + * + * This method needs to be called before the first logging event has + * occured. If this method is not called before then the default + * configuration will be used. + * + * @param string|array $configuration Either a path to the configuration + * file, or a configuration array. + * + * @param string|LoggerConfigurator $configurator A custom + * configurator class: either a class name (string), or an object which + * implements the LoggerConfigurator interface. If left empty, the default + * configurator implementation will be used. + */ + public static function configure($configuration = null, $configurator = null) { + self::resetConfiguration(); + $configurator = self::getConfigurator($configurator); + $configurator->configure(self::getHierarchy(), $configuration); + self::$initialized = true; + } + + /** + * Creates a logger configurator instance based on the provided + * configurator class. If no class is given, returns an instance of + * the default configurator. + * + * @param string|LoggerConfigurator $configurator The configurator class + * or LoggerConfigurator instance. + */ + private static function getConfigurator($configurator = null) { + if ($configurator === null) { + return new LoggerConfiguratorDefault(); + } + + if (is_object($configurator)) { + if ($configurator instanceof LoggerConfigurator) { + return $configurator; + } else { + trigger_error("log4php: Given configurator object [$configurator] does not implement the LoggerConfigurator interface. Reverting to default configurator.", E_USER_WARNING); + return new LoggerConfiguratorDefault(); + } + } + + if (is_string($configurator)) { + if (!class_exists($configurator)) { + trigger_error("log4php: Specified configurator class [$configurator] does not exist. Reverting to default configurator.", E_USER_WARNING); + return new LoggerConfiguratorDefault(); + } + + $instance = new $configurator(); + + if (!($instance instanceof LoggerConfigurator)) { + trigger_error("log4php: Specified configurator class [$configurator] does not implement the LoggerConfigurator interface. Reverting to default configurator.", E_USER_WARNING); + return new LoggerConfiguratorDefault(); + } + + return $instance; + } + + trigger_error("log4php: Invalid configurator specified. Expected either a string or a LoggerConfigurator instance. Reverting to default configurator.", E_USER_WARNING); + return new LoggerConfiguratorDefault(); + } + + /** + * Returns true if the log4php framework has been initialized. + * @return boolean + */ + private static function isInitialized() { + return self::$initialized; + } +} diff --git a/sources/lib/Log4php/LoggerAppender.php b/sources/lib/Log4php/LoggerAppender.php new file mode 100644 index 0000000..aef8a78 --- /dev/null +++ b/sources/lib/Log4php/LoggerAppender.php @@ -0,0 +1,286 @@ +name = $name; + + if ($this->requiresLayout) { + $this->layout = $this->getDefaultLayout(); + } + } + + public function __destruct() { + $this->close(); + } + + /** + * Returns the default layout for this appender. Can be overriden by + * derived appenders. + * + * @return LoggerLayout + */ + public function getDefaultLayout() { + return new LoggerLayoutSimple(); + } + + /** + * Adds a filter to the end of the filter chain. + * @param LoggerFilter $filter add a new LoggerFilter + */ + public function addFilter($filter) { + if($this->filter === null) { + $this->filter = $filter; + } else { + $this->filter->addNext($filter); + } + } + + /** + * Clears the filter chain by removing all the filters in it. + */ + public function clearFilters() { + $this->filter = null; + } + + /** + * Returns the first filter in the filter chain. + * The return value may be null if no is filter is set. + * @return LoggerFilter + */ + public function getFilter() { + return $this->filter; + } + + /** + * Returns the first filter in the filter chain. + * The return value may be null if no is filter is set. + * @return LoggerFilter + */ + public function getFirstFilter() { + return $this->filter; + } + + /** + * Performs threshold checks and invokes filters before delegating logging + * to the subclass' specific append() method. + * @see LoggerAppender::append() + * @param LoggerLoggingEvent $event + */ + public function doAppend(LoggerLoggingEvent $event) { + if($this->closed) { + return; + } + + if(!$this->isAsSevereAsThreshold($event->getLevel())) { + return; + } + + $filter = $this->getFirstFilter(); + while($filter !== null) { + switch ($filter->decide($event)) { + case LoggerFilter::DENY: return; + case LoggerFilter::ACCEPT: return $this->append($event); + case LoggerFilter::NEUTRAL: $filter = $filter->getNext(); + } + } + $this->append($event); + } + + /** + * Sets the appender layout. + * @param LoggerLayout $layout + */ + public function setLayout($layout) { + if($this->requiresLayout()) { + $this->layout = $layout; + } + } + + /** + * Returns the appender layout. + * @return LoggerLayout + */ + public function getLayout() { + return $this->layout; + } + + /** + * Configurators call this method to determine if the appender + * requires a layout. + * + *

    If this method returns true, meaning that layout is required, + * then the configurator will configure a layout using the configuration + * information at its disposal. If this method returns false, + * meaning that a layout is not required, then layout configuration will be + * skipped even if there is available layout configuration + * information at the disposal of the configurator.

    + * + *

    In the rather exceptional case, where the appender + * implementation admits a layout but can also work without it, then + * the appender should return true.

    + * + * @return boolean + */ + public function requiresLayout() { + return $this->requiresLayout; + } + + /** + * Retruns the appender name. + * @return string + */ + public function getName() { + return $this->name; + } + + /** + * Sets the appender name. + * @param string $name + */ + public function setName($name) { + $this->name = $name; + } + + /** + * Returns the appender's threshold level. + * @return LoggerLevel + */ + public function getThreshold() { + return $this->threshold; + } + + /** + * Sets the appender threshold. + * + * @param LoggerLevel|string $threshold Either a {@link LoggerLevel} + * object or a string equivalent. + * @see LoggerOptionConverter::toLevel() + */ + public function setThreshold($threshold) { + $this->setLevel('threshold', $threshold); + } + + /** + * Checks whether the message level is below the appender's threshold. + * + * If there is no threshold set, then the return value is always true. + * + * @param LoggerLevel $level + * @return boolean Returns true if level is greater or equal than + * threshold, or if the threshold is not set. Otherwise returns false. + */ + public function isAsSevereAsThreshold($level) { + if($this->threshold === null) { + return true; + } + return $level->isGreaterOrEqual($this->getThreshold()); + } + + /** + * Prepares the appender for logging. + * + * Derived appenders should override this method if option structure + * requires it. + */ + public function activateOptions() { + $this->closed = false; + } + + /** + * Forwards the logging event to the destination. + * + * Derived appenders should implement this method to perform actual logging. + * + * @param LoggerLoggingEvent $event + */ + abstract protected function append(LoggerLoggingEvent $event); + + /** + * Releases any resources allocated by the appender. + * + * Derived appenders should override this method to perform proper closing + * procedures. + */ + public function close() { + $this->closed = true; + } + + /** Triggers a warning for this logger with the given message. */ + protected function warn($message) { + $id = get_class($this) . (empty($this->name) ? '' : ":{$this->name}"); + trigger_error("log4php: [$id]: $message", E_USER_WARNING); + } + +} diff --git a/sources/lib/Log4php/LoggerAppenderPool.php b/sources/lib/Log4php/LoggerAppenderPool.php new file mode 100644 index 0000000..b3fbb17 --- /dev/null +++ b/sources/lib/Log4php/LoggerAppenderPool.php @@ -0,0 +1,98 @@ +getName(); + + if(empty($name)) { + trigger_error('log4php: Cannot add unnamed appender to pool.', E_USER_WARNING); + return; + } + + if (isset(self::$appenders[$name])) { + trigger_error("log4php: Appender [$name] already exists in pool. Overwriting existing appender.", E_USER_WARNING); + } + + self::$appenders[$name] = $appender; + } + + /** + * Retrieves an appender from the pool by name. + * @param string $name Name of the appender to retrieve. + * @return LoggerAppender The named appender or NULL if no such appender + * exists in the pool. + */ + public static function get($name) { + return isset(self::$appenders[$name]) ? self::$appenders[$name] : null; + } + + /** + * Removes an appender from the pool by name. + * @param string $name Name of the appender to remove. + */ + public static function delete($name) { + unset(self::$appenders[$name]); + } + + /** + * Returns all appenders from the pool. + * @return array Array of LoggerAppender objects. + */ + public static function getAppenders() { + return self::$appenders; + } + + /** + * Checks whether an appender exists in the pool. + * @param string $name Name of the appender to look for. + * @return boolean TRUE if the appender with the given name exists. + */ + public static function exists($name) { + return isset(self::$appenders[$name]); + } + + /** + * Clears all appenders from the pool. + */ + public static function clear() { + self::$appenders = array(); + } +} \ No newline at end of file diff --git a/sources/lib/Log4php/LoggerAutoloader.php b/sources/lib/Log4php/LoggerAutoloader.php new file mode 100644 index 0000000..b6d66bd --- /dev/null +++ b/sources/lib/Log4php/LoggerAutoloader.php @@ -0,0 +1,142 @@ + '/LoggerAppender.php', + 'LoggerAppenderPool' => '/LoggerAppenderPool.php', + 'LoggerConfigurable' => '/LoggerConfigurable.php', + 'LoggerConfigurator' => '/LoggerConfigurator.php', + 'LoggerException' => '/LoggerException.php', + 'LoggerFilter' => '/LoggerFilter.php', + 'LoggerHierarchy' => '/LoggerHierarchy.php', + 'LoggerLevel' => '/LoggerLevel.php', + 'LoggerLocationInfo' => '/LoggerLocationInfo.php', + 'LoggerLoggingEvent' => '/LoggerLoggingEvent.php', + 'LoggerMDC' => '/LoggerMDC.php', + 'LoggerNDC' => '/LoggerNDC.php', + 'LoggerLayout' => '/LoggerLayout.php', + 'LoggerReflectionUtils' => '/LoggerReflectionUtils.php', + 'LoggerRoot' => '/LoggerRoot.php', + 'LoggerThrowableInformation' => '/LoggerThrowableInformation.php', + + // Appenders + 'LoggerAppenderConsole' => '/appenders/LoggerAppenderConsole.php', + 'LoggerAppenderDailyFile' => '/appenders/LoggerAppenderDailyFile.php', + 'LoggerAppenderEcho' => '/appenders/LoggerAppenderEcho.php', + 'LoggerAppenderFile' => '/appenders/LoggerAppenderFile.php', + 'LoggerAppenderMail' => '/appenders/LoggerAppenderMail.php', + 'LoggerAppenderMailEvent' => '/appenders/LoggerAppenderMailEvent.php', + 'LoggerAppenderMongoDB' => '/appenders/LoggerAppenderMongoDB.php', + 'LoggerAppenderNull' => '/appenders/LoggerAppenderNull.php', + 'LoggerAppenderFirePHP' => '/appenders/LoggerAppenderFirePHP.php', + 'LoggerAppenderPDO' => '/appenders/LoggerAppenderPDO.php', + 'LoggerAppenderPhp' => '/appenders/LoggerAppenderPhp.php', + 'LoggerAppenderRollingFile' => '/appenders/LoggerAppenderRollingFile.php', + 'LoggerAppenderSocket' => '/appenders/LoggerAppenderSocket.php', + 'LoggerAppenderSyslog' => '/appenders/LoggerAppenderSyslog.php', + + // Configurators + 'LoggerConfigurationAdapter' => '/configurators/LoggerConfigurationAdapter.php', + 'LoggerConfigurationAdapterINI' => '/configurators/LoggerConfigurationAdapterINI.php', + 'LoggerConfigurationAdapterPHP' => '/configurators/LoggerConfigurationAdapterPHP.php', + 'LoggerConfigurationAdapterXML' => '/configurators/LoggerConfigurationAdapterXML.php', + 'LoggerConfiguratorDefault' => '/configurators/LoggerConfiguratorDefault.php', + + // Filters + 'LoggerFilterDenyAll' => '/filters/LoggerFilterDenyAll.php', + 'LoggerFilterLevelMatch' => '/filters/LoggerFilterLevelMatch.php', + 'LoggerFilterLevelRange' => '/filters/LoggerFilterLevelRange.php', + 'LoggerFilterStringMatch' => '/filters/LoggerFilterStringMatch.php', + + // Helpers + 'LoggerFormattingInfo' => '/helpers/LoggerFormattingInfo.php', + 'LoggerOptionConverter' => '/helpers/LoggerOptionConverter.php', + 'LoggerPatternParser' => '/helpers/LoggerPatternParser.php', + 'LoggerUtils' => '/helpers/LoggerUtils.php', + + // Pattern converters + 'LoggerPatternConverter' => '/pattern/LoggerPatternConverter.php', + 'LoggerPatternConverterClass' => '/pattern/LoggerPatternConverterClass.php', + 'LoggerPatternConverterCookie' => '/pattern/LoggerPatternConverterCookie.php', + 'LoggerPatternConverterDate' => '/pattern/LoggerPatternConverterDate.php', + 'LoggerPatternConverterEnvironment' => '/pattern/LoggerPatternConverterEnvironment.php', + 'LoggerPatternConverterFile' => '/pattern/LoggerPatternConverterFile.php', + 'LoggerPatternConverterLevel' => '/pattern/LoggerPatternConverterLevel.php', + 'LoggerPatternConverterLine' => '/pattern/LoggerPatternConverterLine.php', + 'LoggerPatternConverterLiteral' => '/pattern/LoggerPatternConverterLiteral.php', + 'LoggerPatternConverterLocation' => '/pattern/LoggerPatternConverterLocation.php', + 'LoggerPatternConverterLogger' => '/pattern/LoggerPatternConverterLogger.php', + 'LoggerPatternConverterMDC' => '/pattern/LoggerPatternConverterMDC.php', + 'LoggerPatternConverterMessage' => '/pattern/LoggerPatternConverterMessage.php', + 'LoggerPatternConverterMethod' => '/pattern/LoggerPatternConverterMethod.php', + 'LoggerPatternConverterNDC' => '/pattern/LoggerPatternConverterNDC.php', + 'LoggerPatternConverterNewLine' => '/pattern/LoggerPatternConverterNewLine.php', + 'LoggerPatternConverterProcess' => '/pattern/LoggerPatternConverterProcess.php', + 'LoggerPatternConverterRelative' => '/pattern/LoggerPatternConverterRelative.php', + 'LoggerPatternConverterRequest' => '/pattern/LoggerPatternConverterRequest.php', + 'LoggerPatternConverterServer' => '/pattern/LoggerPatternConverterServer.php', + 'LoggerPatternConverterSession' => '/pattern/LoggerPatternConverterSession.php', + 'LoggerPatternConverterSessionID' => '/pattern/LoggerPatternConverterSessionID.php', + 'LoggerPatternConverterSuperglobal' => '/pattern/LoggerPatternConverterSuperglobal.php', + 'LoggerPatternConverterThrowable' => '/pattern/LoggerPatternConverterThrowable.php', + + // Layouts + 'LoggerLayoutHtml' => '/layouts/LoggerLayoutHtml.php', + 'LoggerLayoutPattern' => '/layouts/LoggerLayoutPattern.php', + 'LoggerLayoutSerialized' => '/layouts/LoggerLayoutSerialized.php', + 'LoggerLayoutSimple' => '/layouts/LoggerLayoutSimple.php', + 'LoggerLayoutTTCC' => '/layouts/LoggerLayoutTTCC.php', + 'LoggerLayoutXml' => '/layouts/LoggerLayoutXml.php', + + // Renderers + 'LoggerRendererDefault' => '/renderers/LoggerRendererDefault.php', + 'LoggerRendererException' => '/renderers/LoggerRendererException.php', + 'LoggerRendererMap' => '/renderers/LoggerRendererMap.php', + 'LoggerRenderer' => '/renderers/LoggerRenderer.php', + ); + + /** + * Loads a class. + * @param string $className The name of the class to load. + */ + public static function autoload($className) { + if(isset(self::$classes[$className])) { + include dirname(__FILE__) . self::$classes[$className]; + } + } +} diff --git a/sources/lib/Log4php/LoggerConfigurable.php b/sources/lib/Log4php/LoggerConfigurable.php new file mode 100644 index 0000000..8c6bb66 --- /dev/null +++ b/sources/lib/Log4php/LoggerConfigurable.php @@ -0,0 +1,116 @@ +$property = LoggerOptionConverter::toBooleanEx($value); + } catch (Exception $ex) { + $value = var_export($value, true); + $this->warn("Invalid value given for '$property' property: [$value]. Expected a boolean value. Property not changed."); + } + } + + /** Setter function for integer type. */ + protected function setInteger($property, $value) { + try { + $this->$property = LoggerOptionConverter::toIntegerEx($value); + } catch (Exception $ex) { + $value = var_export($value, true); + $this->warn("Invalid value given for '$property' property: [$value]. Expected an integer. Property not changed."); + } + } + + /** Setter function for LoggerLevel values. */ + protected function setLevel($property, $value) { + try { + $this->$property = LoggerOptionConverter::toLevelEx($value); + } catch (Exception $ex) { + $value = var_export($value, true); + $this->warn("Invalid value given for '$property' property: [$value]. Expected a level value. Property not changed."); + } + } + + /** Setter function for integer type. */ + protected function setPositiveInteger($property, $value) { + try { + $this->$property = LoggerOptionConverter::toPositiveIntegerEx($value); + } catch (Exception $ex) { + $value = var_export($value, true); + $this->warn("Invalid value given for '$property' property: [$value]. Expected a positive integer. Property not changed."); + } + } + + /** Setter for file size. */ + protected function setFileSize($property, $value) { + try { + $this->$property = LoggerOptionConverter::toFileSizeEx($value); + } catch (Exception $ex) { + $value = var_export($value, true); + $this->warn("Invalid value given for '$property' property: [$value]. Expected a file size value. Property not changed."); + } + } + + /** Setter function for numeric type. */ + protected function setNumeric($property, $value) { + try { + $this->$property = LoggerOptionConverter::toNumericEx($value); + } catch (Exception $ex) { + $value = var_export($value, true); + $this->warn("Invalid value given for '$property' property: [$value]. Expected a number. Property not changed."); + } + } + + /** Setter function for string type. */ + protected function setString($property, $value, $nullable = false) { + if ($value === null) { + if($nullable) { + $this->$property= null; + } else { + $this->warn("Null value given for '$property' property. Expected a string. Property not changed."); + } + } else { + try { + $value = LoggerOptionConverter::toStringEx($value); + $this->$property = LoggerOptionConverter::substConstants($value); + } catch (Exception $ex) { + $value = var_export($value, true); + $this->warn("Invalid value given for '$property' property: [$value]. Expected a string. Property not changed."); + } + } + } + + /** Triggers a warning. */ + protected function warn($message) { + $class = get_class($this); + trigger_error("log4php: $class: $message", E_USER_WARNING); + } +} diff --git a/sources/lib/Log4php/LoggerConfigurator.php b/sources/lib/Log4php/LoggerConfigurator.php new file mode 100644 index 0000000..51d6a0f --- /dev/null +++ b/sources/lib/Log4php/LoggerConfigurator.php @@ -0,0 +1,42 @@ +This abstract class assumes and also imposes that filters be + * organized in a linear chain. The {@link #decide + * decide(LoggerLoggingEvent)} method of each filter is called sequentially, + * in the order of their addition to the chain. + * + *

    The {@link decide()} method must return one + * of the integer constants {@link LoggerFilter::DENY}, + * {@link LoggerFilter::NEUTRAL} or {@link LoggerFilter::ACCEPT}. + * + *

    If the value {@link LoggerFilter::DENY} is returned, then the log event is + * dropped immediately without consulting with the remaining + * filters. + * + *

    If the value {@link LoggerFilter::NEUTRAL} is returned, then the next filter + * in the chain is consulted. If there are no more filters in the + * chain, then the log event is logged. Thus, in the presence of no + * filters, the default behaviour is to log all logging events. + * + *

    If the value {@link LoggerFilter::ACCEPT} is returned, then the log + * event is logged without consulting the remaining filters. + * + *

    The philosophy of log4php filters is largely inspired from the + * Linux ipchains. + * + * @version $Revision: 1213283 $ + * @package log4php + */ +abstract class LoggerFilter extends LoggerConfigurable { + + /** + * The log event must be logged immediately without consulting with + * the remaining filters, if any, in the chain. + */ + const ACCEPT = 1; + + /** + * This filter is neutral with respect to the log event. The + * remaining filters, if any, should be consulted for a final decision. + */ + const NEUTRAL = 0; + + /** + * The log event must be dropped immediately without consulting + * with the remaining filters, if any, in the chain. + */ + const DENY = -1; + + /** + * @var LoggerFilter Points to the next {@link LoggerFilter} in the filter chain. + */ + protected $next; + + /** + * Usually filters options become active when set. We provide a + * default do-nothing implementation for convenience. + */ + public function activateOptions() { + } + + /** + * Decide what to do. + *

    If the decision is {@link LoggerFilter::DENY}, then the event will be + * dropped. If the decision is {@link LoggerFilter::NEUTRAL}, then the next + * filter, if any, will be invoked. If the decision is {@link LoggerFilter::ACCEPT} then + * the event will be logged without consulting with other filters in + * the chain. + * + * @param LoggerLoggingEvent $event The {@link LoggerLoggingEvent} to decide upon. + * @return integer {@link LoggerFilter::NEUTRAL} or {@link LoggerFilter::DENY}|{@link LoggerFilter::ACCEPT} + */ + public function decide(LoggerLoggingEvent $event) { + return self::NEUTRAL; + } + + /** + * Adds a new filter to the filter chain this filter is a part of. + * If this filter has already and follow up filter, the param filter + * is passed on until it is the last filter in chain. + * + * @param $filter - the filter to add to this chain + */ + public function addNext($filter) { + if($this->next !== null) { + $this->next->addNext($filter); + } else { + $this->next = $filter; + } + } + + /** + * Returns the next filter in this chain + * @return the next filter + */ + public function getNext() { + return $this->next; + } + +} diff --git a/sources/lib/Log4php/LoggerHierarchy.php b/sources/lib/Log4php/LoggerHierarchy.php new file mode 100644 index 0000000..05a52af --- /dev/null +++ b/sources/lib/Log4php/LoggerHierarchy.php @@ -0,0 +1,257 @@ +The casual user does not have to deal with this class directly.

    + * + *

    The structure of the logger hierarchy is maintained by the + * getLogger method. The hierarchy is such that children link + * to their parent but parents do not have any pointers to their + * children. Moreover, loggers can be instantiated in any order, in + * particular descendant before ancestor.

    + * + *

    In case a descendant is created before a particular ancestor, + * then it creates a provision node for the ancestor and adds itself + * to the provision node. Other descendants of the same ancestor add + * themselves to the previously created provision node.

    + * + * @version $Revision: 1394956 $ + * @package log4php + */ +class LoggerHierarchy { + + /** Array holding all Logger instances. */ + protected $loggers = array(); + + /** + * The root logger. + * @var RootLogger + */ + protected $root; + + /** + * The logger renderer map. + * @var LoggerRendererMap + */ + protected $rendererMap; + + /** + * Main level threshold. Events with lower level will not be logged by any + * logger, regardless of it's configuration. + * @var LoggerLevel + */ + protected $threshold; + + /** + * Creates a new logger hierarchy. + * @param LoggerRoot $root The root logger. + */ + public function __construct(LoggerRoot $root) { + $this->root = $root; + $this->setThreshold(LoggerLevel::getLevelAll()); + $this->rendererMap = new LoggerRendererMap(); + } + + /** + * Clears all loggers. + */ + public function clear() { + $this->loggers = array(); + } + + /** + * Check if the named logger exists in the hierarchy. + * @param string $name + * @return boolean + */ + public function exists($name) { + return isset($this->loggers[$name]); + } + + /** + * Returns all the currently defined loggers in this hierarchy as an array. + * @return array + */ + public function getCurrentLoggers() { + return array_values($this->loggers); + } + + /** + * Returns a named logger instance logger. If it doesn't exist, one is created. + * + * @param string $name Logger name + * @return Logger Logger instance. + */ + public function getLogger($name) { + if(!isset($this->loggers[$name])) { + $logger = new Logger($name); + + $nodes = explode('.', $name); + $firstNode = array_shift($nodes); + + // if name is not a first node but another first node is their + if($firstNode != $name and isset($this->loggers[$firstNode])) { + $logger->setParent($this->loggers[$firstNode]); + } else { + // if there is no father, set root logger as father + $logger->setParent($this->root); + } + + // if there are more nodes than one + if(count($nodes) > 0) { + // find parent node + foreach($nodes as $node) { + $parentNode = "$firstNode.$node"; + if(isset($this->loggers[$parentNode]) and $parentNode != $name) { + $logger->setParent($this->loggers[$parentNode]); + } + $firstNode .= ".$node"; + } + } + + $this->loggers[$name] = $logger; + } + + return $this->loggers[$name]; + } + + /** + * Returns the logger renderer map. + * @return LoggerRendererMap + */ + public function getRendererMap() { + return $this->rendererMap; + } + + /** + * Returns the root logger. + * @return LoggerRoot + */ + public function getRootLogger() { + return $this->root; + } + + /** + * Returns the main threshold level. + * @return LoggerLevel + */ + public function getThreshold() { + return $this->threshold; + } + + /** + * Returns true if the hierarchy is disabled for given log level and false + * otherwise. + * @return boolean + */ + public function isDisabled(LoggerLevel $level) { + return ($this->threshold->toInt() > $level->toInt()); + } + + /** + * Reset all values contained in this hierarchy instance to their + * default. + * + * This removes all appenders from all loggers, sets + * the level of all non-root loggers to null, + * sets their additivity flag to true and sets the level + * of the root logger to {@link LOGGER_LEVEL_DEBUG}. + * + *

    Existing loggers are not removed. They are just reset. + * + *

    This method should be used sparingly and with care as it will + * block all logging until it is completed.

    + */ + public function resetConfiguration() { + $root = $this->getRootLogger(); + + $root->setLevel(LoggerLevel::getLevelDebug()); + $this->setThreshold(LoggerLevel::getLevelAll()); + $this->shutDown(); + + foreach($this->loggers as $logger) { + $logger->setLevel(null); + $logger->setAdditivity(true); + $logger->removeAllAppenders(); + } + + $this->rendererMap->reset(); + LoggerAppenderPool::clear(); + } + + /** + * Sets the main threshold level. + * @param LoggerLevel $l + */ + public function setThreshold(LoggerLevel $threshold) { + $this->threshold = $threshold; + } + + /** + * Shutting down a hierarchy will safely close and remove + * all appenders in all loggers including the root logger. + * + * The shutdown method is careful to close nested + * appenders before closing regular appenders. This is allows + * configurations where a regular appender is attached to a logger + * and again to a nested appender. + * + * @todo Check if the last paragraph is correct. + */ + public function shutdown() { + $this->root->removeAllAppenders(); + + foreach($this->loggers as $logger) { + $logger->removeAllAppenders(); + } + } + + /** + * Prints the current Logger hierarchy tree. Useful for debugging. + */ + public function printHierarchy() { + $this->printHierarchyInner($this->getRootLogger(), 0); + } + + private function printHierarchyInner(Logger $current, $level) { + for ($i = 0; $i < $level; $i++) { + echo ($i == $level - 1) ? "|--" : "| "; + } + echo $current->getName() . "\n"; + + foreach($this->loggers as $logger) { + if ($logger->getParent() == $current) { + $this->printHierarchyInner($logger, $level + 1); + } + } + } +} diff --git a/sources/lib/Log4php/LoggerLayout.php b/sources/lib/Log4php/LoggerLayout.php new file mode 100644 index 0000000..81bf71b --- /dev/null +++ b/sources/lib/Log4php/LoggerLayout.php @@ -0,0 +1,74 @@ +getRenderedMessage(); + } + + /** + * Returns the content type output by this layout. + * @return string + */ + public function getContentType() { + return "text/plain"; + } + + /** + * Returns the footer for the layout format. + * @return string + */ + public function getFooter() { + return null; + } + + /** + * Returns the header for the layout format. + * @return string + */ + public function getHeader() { + return null; + } + + /** Triggers a warning for this layout with the given message. */ + protected function warn($message) { + trigger_error("log4php: [" . get_class($this) . "]: $message", E_USER_WARNING); + } +} diff --git a/sources/lib/Log4php/LoggerLevel.php b/sources/lib/Log4php/LoggerLevel.php new file mode 100644 index 0000000..a76976f --- /dev/null +++ b/sources/lib/Log4php/LoggerLevel.php @@ -0,0 +1,256 @@ +OFF, FATAL, ERROR, + * WARN, INFO, DEBUG and + * ALL. + * + *

    The LoggerLevel class may be subclassed to define a larger + * level set.

    + * + * @version $Revision: 1379729 $ + * @package log4php + * @since 0.5 + */ +class LoggerLevel { + + const OFF = 2147483647; + const FATAL = 50000; + const ERROR = 40000; + const WARN = 30000; + const INFO = 20000; + const DEBUG = 10000; + const TRACE = 5000; + const ALL = -2147483647; + + /** Integer level value. */ + private $level; + + /** Contains a list of instantiated levels. */ + private static $levelMap; + + /** String representation of the level. */ + private $levelStr; + + /** + * Equivalent syslog level. + * @var integer + */ + private $syslogEquivalent; + + /** + * Constructor + * + * @param integer $level + * @param string $levelStr + * @param integer $syslogEquivalent + */ + private function __construct($level, $levelStr, $syslogEquivalent) { + $this->level = $level; + $this->levelStr = $levelStr; + $this->syslogEquivalent = $syslogEquivalent; + } + + /** + * Compares two logger levels. + * + * @param LoggerLevels $other + * @return boolean + */ + public function equals($other) { + if($other instanceof LoggerLevel) { + if($this->level == $other->level) { + return true; + } + } else { + return false; + } + } + + /** + * Returns an Off Level + * @return LoggerLevel + */ + public static function getLevelOff() { + if(!isset(self::$levelMap[LoggerLevel::OFF])) { + self::$levelMap[LoggerLevel::OFF] = new LoggerLevel(LoggerLevel::OFF, 'OFF', LOG_ALERT); + } + return self::$levelMap[LoggerLevel::OFF]; + } + + /** + * Returns a Fatal Level + * @return LoggerLevel + */ + public static function getLevelFatal() { + if(!isset(self::$levelMap[LoggerLevel::FATAL])) { + self::$levelMap[LoggerLevel::FATAL] = new LoggerLevel(LoggerLevel::FATAL, 'FATAL', LOG_ALERT); + } + return self::$levelMap[LoggerLevel::FATAL]; + } + + /** + * Returns an Error Level + * @return LoggerLevel + */ + public static function getLevelError() { + if(!isset(self::$levelMap[LoggerLevel::ERROR])) { + self::$levelMap[LoggerLevel::ERROR] = new LoggerLevel(LoggerLevel::ERROR, 'ERROR', LOG_ERR); + } + return self::$levelMap[LoggerLevel::ERROR]; + } + + /** + * Returns a Warn Level + * @return LoggerLevel + */ + public static function getLevelWarn() { + if(!isset(self::$levelMap[LoggerLevel::WARN])) { + self::$levelMap[LoggerLevel::WARN] = new LoggerLevel(LoggerLevel::WARN, 'WARN', LOG_WARNING); + } + return self::$levelMap[LoggerLevel::WARN]; + } + + /** + * Returns an Info Level + * @return LoggerLevel + */ + public static function getLevelInfo() { + if(!isset(self::$levelMap[LoggerLevel::INFO])) { + self::$levelMap[LoggerLevel::INFO] = new LoggerLevel(LoggerLevel::INFO, 'INFO', LOG_INFO); + } + return self::$levelMap[LoggerLevel::INFO]; + } + + /** + * Returns a Debug Level + * @return LoggerLevel + */ + public static function getLevelDebug() { + if(!isset(self::$levelMap[LoggerLevel::DEBUG])) { + self::$levelMap[LoggerLevel::DEBUG] = new LoggerLevel(LoggerLevel::DEBUG, 'DEBUG', LOG_DEBUG); + } + return self::$levelMap[LoggerLevel::DEBUG]; + } + + /** + * Returns a Trace Level + * @return LoggerLevel + */ + public static function getLevelTrace() { + if(!isset(self::$levelMap[LoggerLevel::TRACE])) { + self::$levelMap[LoggerLevel::TRACE] = new LoggerLevel(LoggerLevel::TRACE, 'TRACE', LOG_DEBUG); + } + return self::$levelMap[LoggerLevel::TRACE]; + } + + /** + * Returns an All Level + * @return LoggerLevel + */ + public static function getLevelAll() { + if(!isset(self::$levelMap[LoggerLevel::ALL])) { + self::$levelMap[LoggerLevel::ALL] = new LoggerLevel(LoggerLevel::ALL, 'ALL', LOG_DEBUG); + } + return self::$levelMap[LoggerLevel::ALL]; + } + + /** + * Return the syslog equivalent of this level as an integer. + * @return integer + */ + public function getSyslogEquivalent() { + return $this->syslogEquivalent; + } + + /** + * Returns true if this level has a higher or equal + * level than the level passed as argument, false + * otherwise. + * + * @param LoggerLevel $other + * @return boolean + */ + public function isGreaterOrEqual($other) { + return $this->level >= $other->level; + } + + /** + * Returns the string representation of this level. + * @return string + */ + public function toString() { + return $this->levelStr; + } + + /** + * Returns the string representation of this level. + * @return string + */ + public function __toString() { + return $this->toString(); + } + + /** + * Returns the integer representation of this level. + * @return integer + */ + public function toInt() { + return $this->level; + } + + /** + * Convert the input argument to a level. If the conversion fails, then + * this method returns the provided default level. + * + * @param mixed $arg The value to convert to level. + * @param LoggerLevel $default Value to return if conversion is not possible. + * @return LoggerLevel + */ + public static function toLevel($arg, $defaultLevel = null) { + if(is_int($arg)) { + switch($arg) { + case self::ALL: return self::getLevelAll(); + case self::TRACE: return self::getLevelTrace(); + case self::DEBUG: return self::getLevelDebug(); + case self::INFO: return self::getLevelInfo(); + case self::WARN: return self::getLevelWarn(); + case self::ERROR: return self::getLevelError(); + case self::FATAL: return self::getLevelFatal(); + case self::OFF: return self::getLevelOff(); + default: return $defaultLevel; + } + } else { + switch(strtoupper($arg)) { + case 'ALL': return self::getLevelAll(); + case 'TRACE': return self::getLevelTrace(); + case 'DEBUG': return self::getLevelDebug(); + case 'INFO': return self::getLevelInfo(); + case 'WARN': return self::getLevelWarn(); + case 'ERROR': return self::getLevelError(); + case 'FATAL': return self::getLevelFatal(); + case 'OFF': return self::getLevelOff(); + default: return $defaultLevel; + } + } + } +} diff --git a/sources/lib/Log4php/LoggerLocationInfo.php b/sources/lib/Log4php/LoggerLocationInfo.php new file mode 100644 index 0000000..e6a8efe --- /dev/null +++ b/sources/lib/Log4php/LoggerLocationInfo.php @@ -0,0 +1,103 @@ +lineNumber = isset($trace['line']) ? $trace['line'] : null; + $this->fileName = isset($trace['file']) ? $trace['file'] : null; + $this->className = isset($trace['class']) ? $trace['class'] : null; + $this->methodName = isset($trace['function']) ? $trace['function'] : null; + $this->fullInfo = $this->getClassName() . '.' . $this->getMethodName() . + '(' . $this->getFileName() . ':' . $this->getLineNumber() . ')'; + } + + /** Returns the caller class name. */ + public function getClassName() { + return ($this->className === null) ? self::LOCATION_INFO_NA : $this->className; + } + + /** Returns the caller file name. */ + public function getFileName() { + return ($this->fileName === null) ? self::LOCATION_INFO_NA : $this->fileName; + } + + /** Returns the caller line number. */ + public function getLineNumber() { + return ($this->lineNumber === null) ? self::LOCATION_INFO_NA : $this->lineNumber; + } + + /** Returns the caller method name. */ + public function getMethodName() { + return ($this->methodName === null) ? self::LOCATION_INFO_NA : $this->methodName; + } + + /** Returns the full information of the caller. */ + public function getFullInfo() { + return ($this->fullInfo === null) ? self::LOCATION_INFO_NA : $this->fullInfo; + } + +} diff --git a/sources/lib/Log4php/LoggerLoggingEvent.php b/sources/lib/Log4php/LoggerLoggingEvent.php new file mode 100644 index 0000000..f0a4738 --- /dev/null +++ b/sources/lib/Log4php/LoggerLoggingEvent.php @@ -0,0 +1,368 @@ +fqcn = $fqcn; + if($logger instanceof Logger) { + $this->logger = $logger; + $this->categoryName = $logger->getName(); + } else { + $this->categoryName = strval($logger); + } + $this->level = $level; + $this->message = $message; + if($timeStamp !== null && is_numeric($timeStamp)) { + $this->timeStamp = $timeStamp; + } else { + $this->timeStamp = microtime(true); + } + + if ($throwable !== null && $throwable instanceof Exception) { + $this->throwableInfo = new LoggerThrowableInformation($throwable); + } + } + + /** + * Returns the full qualified classname. + * TODO: PHP does contain namespaces in 5.3. Those should be returned too, + */ + public function getFullQualifiedClassname() { + return $this->fqcn; + } + + /** + * Set the location information for this logging event. The collected + * information is cached for future use. + * + *

    This method uses {@link PHP_MANUAL#debug_backtrace debug_backtrace()} function (if exists) + * to collect informations about caller.

    + *

    It only recognize informations generated by {@link Logger} and its subclasses.

    + * @return LoggerLocationInfo + */ + public function getLocationInformation() { + if($this->locationInfo === null) { + + $locationInfo = array(); + $trace = debug_backtrace(); + $prevHop = null; + // make a downsearch to identify the caller + $hop = array_pop($trace); + while($hop !== null) { + if(isset($hop['class'])) { + // we are sometimes in functions = no class available: avoid php warning here + $className = strtolower($hop['class']); + if(!empty($className) and ($className == 'logger' or + strtolower(get_parent_class($className)) == 'logger')) { + $locationInfo['line'] = $hop['line']; + $locationInfo['file'] = $hop['file']; + break; + } + } + $prevHop = $hop; + $hop = array_pop($trace); + } + $locationInfo['class'] = isset($prevHop['class']) ? $prevHop['class'] : 'main'; + if(isset($prevHop['function']) and + $prevHop['function'] !== 'include' and + $prevHop['function'] !== 'include_once' and + $prevHop['function'] !== 'require' and + $prevHop['function'] !== 'require_once') { + + $locationInfo['function'] = $prevHop['function']; + } else { + $locationInfo['function'] = 'main'; + } + + $this->locationInfo = new LoggerLocationInfo($locationInfo, $this->fqcn); + } + return $this->locationInfo; + } + + /** + * Return the level of this event. Use this form instead of directly + * accessing the {@link $level} field. + * @return LoggerLevel + */ + public function getLevel() { + return $this->level; + } + + /** + * Returns the logger which created the event. + * @return Logger + */ + public function getLogger() { + return $this->logger; + } + + /** + * Return the name of the logger. Use this form instead of directly + * accessing the {@link $categoryName} field. + * @return string + */ + public function getLoggerName() { + return $this->categoryName; + } + + /** + * Return the message for this logging event. + * @return mixed + */ + public function getMessage() { + return $this->message; + } + + /** + * This method returns the NDC for this event. It will return the + * correct content even if the event was generated in a different + * thread or even on a different machine. The {@link LoggerNDC::get()} method + * should never be called directly. + * @return string + */ + public function getNDC() { + if($this->ndcLookupRequired) { + $this->ndcLookupRequired = false; + $this->ndc = LoggerNDC::get(); + } + return $this->ndc; + } + + /** + * Returns the the context corresponding to the key + * parameter. + * @return string + */ + public function getMDC($key) { + return LoggerMDC::get($key); + } + + /** + * Returns the entire MDC context. + * @return array + */ + public function getMDCMap () { + return LoggerMDC::getMap(); + } + + /** + * Render message. + * @return string + */ + public function getRenderedMessage() { + if($this->renderedMessage === null and $this->message !== null) { + if(is_string($this->message)) { + $this->renderedMessage = $this->message; + } else { + $rendererMap = Logger::getHierarchy()->getRendererMap(); + $this->renderedMessage= $rendererMap->findAndRender($this->message); + } + } + return $this->renderedMessage; + } + + /** + * Returns the time when the application started, as a UNIX timestamp + * with microseconds. + * @return float + */ + public static function getStartTime() { + if(!isset(self::$startTime)) { + self::$startTime = microtime(true); + } + return self::$startTime; + } + + /** + * @return float + */ + public function getTimeStamp() { + return $this->timeStamp; + } + + /** + * Returns the time in seconds passed from the beginning of execution to + * the time the event was constructed. + * + * @return float Seconds with microseconds in decimals. + */ + public function getRelativeTime() { + return $this->timeStamp - self::$startTime; + } + + /** + * Returns the time in milliseconds passed from the beginning of execution + * to the time the event was constructed. + * + * @deprecated This method has been replaced by getRelativeTime which + * does not perform unneccesary multiplication and formatting. + * + * @return integer + */ + public function getTime() { + $eventTime = $this->getTimeStamp(); + $eventStartTime = LoggerLoggingEvent::getStartTime(); + return number_format(($eventTime - $eventStartTime) * 1000, 0, '', ''); + } + + /** + * @return mixed + */ + public function getThreadName() { + if ($this->threadName === null) { + $this->threadName = (string)getmypid(); + } + return $this->threadName; + } + + /** + * @return mixed LoggerThrowableInformation + */ + public function getThrowableInformation() { + return $this->throwableInfo; + } + + /** + * Serialize this object + * @return string + */ + public function toString() { + serialize($this); + } + + /** + * Avoid serialization of the {@link $logger} object + */ + public function __sleep() { + return array( + 'fqcn', + 'categoryName', + 'level', + 'ndc', + 'ndcLookupRequired', + 'message', + 'renderedMessage', + 'threadName', + 'timeStamp', + 'locationInfo', + ); + } + +} + +LoggerLoggingEvent::getStartTime(); diff --git a/sources/lib/Log4php/LoggerMDC.php b/sources/lib/Log4php/LoggerMDC.php new file mode 100644 index 0000000..f5d3283 --- /dev/null +++ b/sources/lib/Log4php/LoggerMDC.php @@ -0,0 +1,88 @@ +nested diagnostic contexts. + * + * NDC was defined by Neil Harrison in the article "Patterns for Logging + * Diagnostic Messages" part of the book "Pattern Languages of + * Program Design 3" edited by Martin et al. + * + * A Nested Diagnostic Context, or NDC in short, is an instrument + * to distinguish interleaved log output from different sources. Log + * output is typically interleaved when a server handles multiple + * clients near-simultaneously. + * + * This class is similar to the {@link LoggerMDC} class except that it is + * based on a stack instead of a map. + * + * Interleaved log output can still be meaningful if each log entry + * from different contexts had a distinctive stamp. This is where NDCs + * come into play. + * + * Note that NDCs are managed on a per thread basis. + * + * NDC operations such as {@link push()}, {@link pop()}, + * {@link clear()}, {@link getDepth()} and {@link setMaxDepth()} + * affect the NDC of the current thread only. NDCs of other + * threads remain unaffected. + * + * For example, a servlet can build a per client request NDC + * consisting the clients host name and other information contained in + * the the request. Cookies are another source of distinctive + * information. To build an NDC one uses the {@link push()} + * operation. + * + * Simply put, + * + * - Contexts can be nested. + * - When entering a context, call LoggerNDC::push() + * As a side effect, if there is no nested diagnostic context for the + * current thread, this method will create it. + * - When leaving a context, call LoggerNDC::pop() + * - When exiting a thread make sure to call {@link remove()} + * + * There is no penalty for forgetting to match each + * push operation with a corresponding pop, + * except the obvious mismatch between the real application context + * and the context set in the NDC. + * + * If configured to do so, {@link LoggerPatternLayout} and {@link LoggerLayoutTTCC} + * instances automatically retrieve the nested diagnostic + * context for the current thread without any user intervention. + * Hence, even if a servlet is serving multiple clients + * simultaneously, the logs emanating from the same code (belonging to + * the same category) can still be distinguished because each client + * request will have a different NDC tag. + * + * Example: + * + * {@example ../../examples/php/ndc.php 19}
    + * + * With the properties file: + * + * {@example ../../examples/resources/ndc.properties 18}
    + * + * Will result in the following (notice the conn and client ids): + * + *
    + * 2009-09-13 19:04:27 DEBUG root conn=1234: just received a new connection in src/examples/php/ndc.php at 23
    + * 2009-09-13 19:04:27 DEBUG root conn=1234 client=ab23: some more messages that can in src/examples/php/ndc.php at 25
    + * 2009-09-13 19:04:27 DEBUG root conn=1234 client=ab23: now related to a client in src/examples/php/ndc.php at 26
    + * 2009-09-13 19:04:27 DEBUG root : back and waiting for new connections in src/examples/php/ndc.php at 29
    + * 
    + * + * @version $Revision: 1350602 $ + * @package log4php + * @since 0.3 + */ +class LoggerNDC { + + /** This is the repository of NDC stack */ + private static $stack = array(); + + /** + * Clear any nested diagnostic information if any. This method is + * useful in cases where the same thread can be potentially used + * over and over in different unrelated contexts. + * + *

    This method is equivalent to calling the {@link setMaxDepth()} + * method with a zero maxDepth argument. + */ + public static function clear() { + self::$stack = array(); + } + + /** + * Never use this method directly, use the {@link LoggerLoggingEvent::getNDC()} method instead. + * @return array + */ + public static function get() { + return implode(' ', self::$stack); + } + + /** + * Get the current nesting depth of this diagnostic context. + * + * @see setMaxDepth() + * @return integer + */ + public static function getDepth() { + return count(self::$stack); + } + + /** + * Clients should call this method before leaving a diagnostic + * context. + * + *

    The returned value is the value that was pushed last. If no + * context is available, then the empty string "" is returned.

    + * + * @return string The innermost diagnostic context. + */ + public static function pop() { + if(count(self::$stack) > 0) { + return array_pop(self::$stack); + } else { + return ''; + } + } + + /** + * Looks at the last diagnostic context at the top of this NDC + * without removing it. + * + *

    The returned value is the value that was pushed last. If no + * context is available, then the empty string "" is returned.

    + * @return string The innermost diagnostic context. + */ + public static function peek() { + if(count(self::$stack) > 0) { + return end(self::$stack); + } else { + return ''; + } + } + + /** + * Push new diagnostic context information for the current thread. + * + *

    The contents of the message parameter is + * determined solely by the client. + * + * @param string $message The new diagnostic context information. + */ + public static function push($message) { + array_push(self::$stack, (string)$message); + } + + /** + * Remove the diagnostic context for this thread. + */ + public static function remove() { + LoggerNDC::clear(); + } + + /** + * Set maximum depth of this diagnostic context. If the current + * depth is smaller or equal to maxDepth, then no + * action is taken. + * + *

    This method is a convenient alternative to multiple + * {@link pop()} calls. Moreover, it is often the case that at + * the end of complex call sequences, the depth of the NDC is + * unpredictable. The {@link setMaxDepth()} method circumvents + * this problem. + * + * @param integer $maxDepth + * @see getDepth() + */ + public static function setMaxDepth($maxDepth) { + $maxDepth = (int)$maxDepth; + if(LoggerNDC::getDepth() > $maxDepth) { + self::$stack = array_slice(self::$stack, 0, $maxDepth); + } + } +} diff --git a/sources/lib/Log4php/LoggerReflectionUtils.php b/sources/lib/Log4php/LoggerReflectionUtils.php new file mode 100644 index 0000000..e046185 --- /dev/null +++ b/sources/lib/Log4php/LoggerReflectionUtils.php @@ -0,0 +1,152 @@ +obj = $obj; + } + + /** + * Set the properties of an object passed as a parameter in one + * go. The properties are parsed relative to a + * prefix. + * + * @param object $obj The object to configure. + * @param array $properties An array containing keys and values. + * @param string $prefix Only keys having the specified prefix will be set. + */ + // TODO: check, if this is really useful + public static function setPropertiesByObject($obj, $properties, $prefix) { + $pSetter = new LoggerReflectionUtils($obj); + return $pSetter->setProperties($properties, $prefix); + } + + + /** + * Set the properites for the object that match the + * prefix passed as parameter. + * + * Example: + * + * $arr['xxxname'] = 'Joe'; + * $arr['xxxmale'] = true; + * and prefix xxx causes setName and setMale. + * + * @param array $properties An array containing keys and values. + * @param string $prefix Only keys having the specified prefix will be set. + */ + public function setProperties($properties, $prefix) { + $len = strlen($prefix); + reset($properties); + while(list($key,) = each($properties)) { + if(strpos($key, $prefix) === 0) { + if(strpos($key, '.', ($len + 1)) > 0) { + continue; + } + $value = $properties[$key]; + $key = substr($key, $len); + if($key == 'layout' and ($this->obj instanceof LoggerAppender)) { + continue; + } + $this->setProperty($key, $value); + } + } + $this->activate(); + } + + /** + * Set a property on this PropertySetter's Object. If successful, this + * method will invoke a setter method on the underlying Object. The + * setter is the one for the specified property name and the value is + * determined partly from the setter argument type and partly from the + * value specified in the call to this method. + * + *

    If the setter expects a String no conversion is necessary. + * If it expects an int, then an attempt is made to convert 'value' + * to an int using new Integer(value). If the setter expects a boolean, + * the conversion is by new Boolean(value). + * + * @param string $name name of the property + * @param string $value String value of the property + */ + public function setProperty($name, $value) { + if($value === null) { + return; + } + + $method = "set" . ucfirst($name); + + if(!method_exists($this->obj, $method)) { + throw new Exception("Error setting log4php property $name to $value: no method $method in class ".get_class($this->obj)."!"); + } else { + return call_user_func(array($this->obj, $method), $value); + } + } + + public function activate() { + if(method_exists($this->obj, 'activateoptions')) { + return call_user_func(array($this->obj, 'activateoptions')); + } + } + + /** + * Creates an instances from the given class name. + * + * @param string $classname + * @return an object from the class with the given classname + */ + public static function createObject($class) { + if(!empty($class)) { + return new $class(); + } + return null; + } + + /** + * @param object $object + * @param string $name + * @param mixed $value + */ + public static function setter($object, $name, $value) { + if (empty($name)) { + return false; + } + $methodName = 'set'.ucfirst($name); + if (method_exists($object, $methodName)) { + return call_user_func(array($object, $methodName), $value); + } else { + return false; + } + } + +} diff --git a/sources/lib/Log4php/LoggerRoot.php b/sources/lib/Log4php/LoggerRoot.php new file mode 100644 index 0000000..0ed27b4 --- /dev/null +++ b/sources/lib/Log4php/LoggerRoot.php @@ -0,0 +1,71 @@ +setLevel($level); + } + + /** + * @return LoggerLevel the level + */ + public function getEffectiveLevel() { + return $this->getLevel(); + } + + /** + * Override level setter to prevent setting the root logger's level to + * null. Root logger must always have a level. + * + * @param LoggerLevel $level + */ + public function setLevel(LoggerLevel $level = null) { + if (isset($level)) { + parent::setLevel($level); + } else { + trigger_error("log4php: Cannot set LoggerRoot level to null.", E_USER_WARNING); + } + } + + /** + * Override parent setter. Root logger cannot have a parent. + * @param Logger $parent + */ + public function setParent(Logger $parent) { + trigger_error("log4php: LoggerRoot cannot have a parent.", E_USER_WARNING); + } +} diff --git a/sources/lib/Log4php/LoggerThrowableInformation.php b/sources/lib/Log4php/LoggerThrowableInformation.php new file mode 100644 index 0000000..20bf758 --- /dev/null +++ b/sources/lib/Log4php/LoggerThrowableInformation.php @@ -0,0 +1,68 @@ +throwable = $throwable; + } + + /** + * Return source exception + * + * @return Exception + */ + public function getThrowable() { + return $this->throwable; + } + + /** + * @desc Returns string representation of throwable + * + * @return array + */ + public function getStringRepresentation() { + if (!is_array($this->throwableArray)) { + $renderer = new LoggerRendererException(); + + $this->throwableArray = explode("\n", $renderer->render($this->throwable)); + } + + return $this->throwableArray; + } +} diff --git a/sources/lib/Log4php/appenders/LoggerAppenderConsole.php b/sources/lib/Log4php/appenders/LoggerAppenderConsole.php new file mode 100644 index 0000000..6f38060 --- /dev/null +++ b/sources/lib/Log4php/appenders/LoggerAppenderConsole.php @@ -0,0 +1,103 @@ +fp = fopen($this->target, 'w'); + if(is_resource($this->fp) && $this->layout !== null) { + fwrite($this->fp, $this->layout->getHeader()); + } + $this->closed = (bool)is_resource($this->fp) === false; + } + + + public function close() { + if($this->closed != true) { + if (is_resource($this->fp) && $this->layout !== null) { + fwrite($this->fp, $this->layout->getFooter()); + fclose($this->fp); + } + $this->closed = true; + } + } + + public function append(LoggerLoggingEvent $event) { + if (is_resource($this->fp) && $this->layout !== null) { + fwrite($this->fp, $this->layout->format($event)); + } + } + + /** + * Sets the 'target' parameter. + * @param string $target + */ + public function setTarget($target) { + $value = trim($target); + if ($value == self::STDOUT || strtoupper($value) == 'STDOUT') { + $this->target = self::STDOUT; + } elseif ($value == self::STDERR || strtoupper($value) == 'STDERR') { + $this->target = self::STDERR; + } else { + $target = var_export($target); + $this->warn("Invalid value given for 'target' property: [$target]. Property not set."); + } + } + + /** + * Returns the value of the 'target' parameter. + * @return string + */ + public function getTarget() { + return $this->target; + } +} diff --git a/sources/lib/Log4php/appenders/LoggerAppenderDailyFile.php b/sources/lib/Log4php/appenders/LoggerAppenderDailyFile.php new file mode 100644 index 0000000..af349aa --- /dev/null +++ b/sources/lib/Log4php/appenders/LoggerAppenderDailyFile.php @@ -0,0 +1,130 @@ +datePattern)) { + $this->warn("Required parameter 'datePattern' not set. Closing appender."); + $this->closed = true; + return; + } + } + + /** + * Appends a logging event. + * + * If the target file changes because of passage of time (e.g. at midnight) + * the current file is closed. A new file, with the new date, will be + * opened by the write() method. + */ + public function append(LoggerLoggingEvent $event) { + $eventDate = $this->getDate($event->getTimestamp()); + + // Initial setting of current date + if (!isset($this->currentDate)) { + $this->currentDate = $eventDate; + } + + // Check if rollover is needed + else if ($this->currentDate !== $eventDate) { + $this->currentDate = $eventDate; + + // Close the file if it's open. + // Note: $this->close() is not called here because it would set + // $this->closed to true and the appender would not recieve + // any more logging requests + if (is_resource($this->fp)) { + $this->write($this->layout->getFooter()); + fclose($this->fp); + } + $this->fp = null; + } + + parent::append($event); + } + + /** Renders the date using the configured datePattern. */ + protected function getDate($timestamp = null) { + return date($this->datePattern, $timestamp); + } + + /** + * Determines target file. Replaces %s in file path with a date. + */ + protected function getTargetFile() { + return str_replace('%s', $this->currentDate, $this->file); + } + + /** + * Sets the 'datePattern' parameter. + * @param string $datePattern + */ + public function setDatePattern($datePattern) { + $this->setString('datePattern', $datePattern); + } + + /** + * Returns the 'datePattern' parameter. + * @return string + */ + public function getDatePattern() { + return $this->datePattern; + } +} diff --git a/sources/lib/Log4php/appenders/LoggerAppenderEcho.php b/sources/lib/Log4php/appenders/LoggerAppenderEcho.php new file mode 100644 index 0000000..d66bdaf --- /dev/null +++ b/sources/lib/Log4php/appenders/LoggerAppenderEcho.php @@ -0,0 +1,88 @@ + element will be inserted + * before each line break in the logged message. Default is false. + * + * @version $Revision: 1337820 $ + * @package log4php + * @subpackage appenders + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0 + * @link http://logging.apache.org/log4php/docs/appenders/echo.html Appender documentation + */ +class LoggerAppenderEcho extends LoggerAppender { + /** + * Used to mark first append. Set to false after first append. + * @var boolean + */ + protected $firstAppend = true; + + /** + * If set to true, a
    element will be inserted before each line + * break in the logged message. Default value is false. @var boolean + */ + protected $htmlLineBreaks = false; + + public function close() { + if($this->closed != true) { + if(!$this->firstAppend) { + echo $this->layout->getFooter(); + } + } + $this->closed = true; + } + + public function append(LoggerLoggingEvent $event) { + if($this->layout !== null) { + if($this->firstAppend) { + echo $this->layout->getHeader(); + $this->firstAppend = false; + } + $text = $this->layout->format($event); + + if ($this->htmlLineBreaks) { + $text = nl2br($text); + } + echo $text; + } + } + + /** + * Sets the 'htmlLineBreaks' parameter. + * @param boolean $value + */ + public function setHtmlLineBreaks($value) { + $this->setBoolean('htmlLineBreaks', $value); + } + + /** + * Returns the 'htmlLineBreaks' parameter. + * @returns boolean + */ + public function getHtmlLineBreaks() { + return $this->htmlLineBreaks; + } +} + diff --git a/sources/lib/Log4php/appenders/LoggerAppenderFile.php b/sources/lib/Log4php/appenders/LoggerAppenderFile.php new file mode 100644 index 0000000..12d44aa --- /dev/null +++ b/sources/lib/Log4php/appenders/LoggerAppenderFile.php @@ -0,0 +1,225 @@ +file; + } + + /** + * Acquires the target file resource, creates the destination folder if + * necessary. Writes layout header to file. + * + * @return boolean FALSE if opening failed + */ + protected function openFile() { + $file = $this->getTargetFile(); + + // Create the target folder if needed + if(!is_file($file)) { + $dir = dirname($file); + + if(!is_dir($dir)) { + $success = mkdir($dir, 0777, true); + if ($success === false) { + $this->warn("Failed creating target directory [$dir]. Closing appender."); + $this->closed = true; + return false; + } + } + } + + $mode = $this->append ? 'a' : 'w'; + $this->fp = fopen($file, $mode); + if ($this->fp === false) { + $this->warn("Failed opening target file. Closing appender."); + $this->fp = null; + $this->closed = true; + return false; + } + + // Required when appending with concurrent access + if($this->append) { + fseek($this->fp, 0, SEEK_END); + } + + // Write the header + $this->write($this->layout->getHeader()); + } + + /** + * Writes a string to the target file. Opens file if not already open. + * @param string $string Data to write. + */ + protected function write($string) { + // Lazy file open + if(!isset($this->fp)) { + if ($this->openFile() === false) { + return; // Do not write if file open failed. + } + } + + if ($this->locking) { + $this->writeWithLocking($string); + } else { + $this->writeWithoutLocking($string); + } + } + + protected function writeWithLocking($string) { + if(flock($this->fp, LOCK_EX)) { + if(fwrite($this->fp, $string) === false) { + $this->warn("Failed writing to file. Closing appender."); + $this->closed = true; + } + flock($this->fp, LOCK_UN); + } else { + $this->warn("Failed locking file for writing. Closing appender."); + $this->closed = true; + } + } + + protected function writeWithoutLocking($string) { + if(fwrite($this->fp, $string) === false) { + $this->warn("Failed writing to file. Closing appender."); + $this->closed = true; + } + } + + public function activateOptions() { + if (empty($this->file)) { + $this->warn("Required parameter 'file' not set. Closing appender."); + $this->closed = true; + return; + } + } + + public function close() { + if (is_resource($this->fp)) { + $this->write($this->layout->getFooter()); + fclose($this->fp); + } + $this->fp = null; + $this->closed = true; + } + + public function append(LoggerLoggingEvent $event) { + $this->write($this->layout->format($event)); + } + + /** + * Sets the 'file' parameter. + * @param string $file + */ + public function setFile($file) { + $this->setString('file', $file); + } + + /** + * Returns the 'file' parameter. + * @return string + */ + public function getFile() { + return $this->file; + } + + /** + * Returns the 'append' parameter. + * @return boolean + */ + public function getAppend() { + return $this->append; + } + + /** + * Sets the 'append' parameter. + * @param boolean $append + */ + public function setAppend($append) { + $this->setBoolean('append', $append); + } + + /** + * Sets the 'file' parmeter. Left for legacy reasons. + * @param string $fileName + * @deprecated Use setFile() instead. + */ + public function setFileName($fileName) { + $this->setFile($fileName); + } + + /** + * Returns the 'file' parmeter. Left for legacy reasons. + * @return string + * @deprecated Use getFile() instead. + */ + public function getFileName() { + return $this->getFile(); + } +} diff --git a/sources/lib/Log4php/appenders/LoggerAppenderFirePHP.php b/sources/lib/Log4php/appenders/LoggerAppenderFirePHP.php new file mode 100644 index 0000000..4601b91 --- /dev/null +++ b/sources/lib/Log4php/appenders/LoggerAppenderFirePHP.php @@ -0,0 +1,100 @@ +console = FirePHP::to($this->target)->console(); + $this->closed = false; + } else { + $this->warn('FirePHP is not installed correctly. Closing appender.'); + } + } + + public function append(LoggerLoggingEvent $event) { + $msg = $event->getMessage(); + + // Skip formatting for objects and arrays which are handled by FirePHP. + if (!is_array($msg) && !is_object($msg)) { + $msg = $this->getLayout()->format($event); + } + + switch ($event->getLevel()->toInt()) { + case LoggerLevel::TRACE: + case LoggerLevel::DEBUG: + $this->console->log($msg); + break; + case LoggerLevel::INFO: + $this->console->info($msg); + break; + case LoggerLevel::WARN: + $this->console->warn($msg); + break; + case LoggerLevel::ERROR: + case LoggerLevel::FATAL: + $this->console->error($msg); + break; + } + } + + /** Returns the target. */ + public function getTarget() { + return $this->target; + } + + /** Sets the target. */ + public function setTarget($target) { + $this->setString('target', $target); + } +} \ No newline at end of file diff --git a/sources/lib/Log4php/appenders/LoggerAppenderMail.php b/sources/lib/Log4php/appenders/LoggerAppenderMail.php new file mode 100644 index 0000000..6204bcb --- /dev/null +++ b/sources/lib/Log4php/appenders/LoggerAppenderMail.php @@ -0,0 +1,136 @@ +layout !== null) { + $this->body .= $this->layout->format($event); + } + } + + public function close() { + if($this->closed != true) { + $from = $this->from; + $to = $this->to; + + if(!empty($this->body) and $from !== null and $to !== null and $this->layout !== null) { + $subject = $this->subject; + if(!$this->dry) { + mail( + $to, $subject, + $this->layout->getHeader() . $this->body . $this->layout->getFooter(), + "From: {$from}\r\n"); + } else { + echo "DRY MODE OF MAIL APP.: Send mail to: ".$to." with content: ".$this->body; + } + } + $this->closed = true; + } + } + + /** Sets the 'subject' parameter. */ + public function setSubject($subject) { + $this->setString('subject', $subject); + } + + /** Returns the 'subject' parameter. */ + public function getSubject() { + return $this->subject; + } + + /** Sets the 'to' parameter. */ + public function setTo($to) { + $this->setString('to', $to); + } + + /** Returns the 'to' parameter. */ + public function getTo() { + return $this->to; + } + + /** Sets the 'from' parameter. */ + public function setFrom($from) { + $this->setString('from', $from); + } + + /** Returns the 'from' parameter. */ + public function getFrom() { + return $this->from; + } + + /** Enables or disables dry mode. */ + public function setDry($dry) { + $this->setBoolean('dry', $dry); + } +} diff --git a/sources/lib/Log4php/appenders/LoggerAppenderMailEvent.php b/sources/lib/Log4php/appenders/LoggerAppenderMailEvent.php new file mode 100644 index 0000000..5e2350d --- /dev/null +++ b/sources/lib/Log4php/appenders/LoggerAppenderMailEvent.php @@ -0,0 +1,180 @@ +to)) { + $this->warn("Required parameter 'to' not set. Closing appender."); + $this->close = true; + return; + } + + $sendmail_from = ini_get('sendmail_from'); + if (empty($this->from) and empty($sendmail_from)) { + $this->warn("Required parameter 'from' not set. Closing appender."); + $this->close = true; + return; + } + + $this->closed = false; + } + + public function append(LoggerLoggingEvent $event) { + $smtpHost = $this->smtpHost; + $prevSmtpHost = ini_get('SMTP'); + if(!empty($smtpHost)) { + ini_set('SMTP', $smtpHost); + } + + $smtpPort = $this->port; + $prevSmtpPort= ini_get('smtp_port'); + if($smtpPort > 0 and $smtpPort < 65535) { + ini_set('smtp_port', $smtpPort); + } + + // On unix only sendmail_path, which is PHP_INI_SYSTEM i.e. not changeable here, is used. + + $addHeader = empty($this->from) ? '' : "From: {$this->from}\r\n"; + + if(!$this->dry) { + $result = mail($this->to, $this->subject, $this->layout->getHeader() . $this->layout->format($event) . $this->layout->getFooter($event), $addHeader); + } else { + echo "DRY MODE OF MAIL APP.: Send mail to: ".$this->to." with additional headers '".trim($addHeader)."' and content: ".$this->layout->format($event); + } + + ini_set('SMTP', $prevSmtpHost); + ini_set('smtp_port', $prevSmtpPort); + } + + /** Sets the 'from' parameter. */ + public function setFrom($from) { + $this->setString('from', $from); + } + + /** Returns the 'from' parameter. */ + public function getFrom() { + return $this->from; + } + + /** Sets the 'port' parameter. */ + public function setPort($port) { + $this->setPositiveInteger('port', $port); + } + + /** Returns the 'port' parameter. */ + public function getPort() { + return $this->port; + } + + /** Sets the 'smtpHost' parameter. */ + public function setSmtpHost($smtpHost) { + $this->setString('smtpHost', $smtpHost); + } + + /** Returns the 'smtpHost' parameter. */ + public function getSmtpHost() { + return $this->smtpHost; + } + + /** Sets the 'subject' parameter. */ + public function setSubject($subject) { + $this->setString('subject', $subject); + } + + /** Returns the 'subject' parameter. */ + public function getSubject() { + return $this->subject; + } + + /** Sets the 'to' parameter. */ + public function setTo($to) { + $this->setString('to', $to); + } + + /** Returns the 'to' parameter. */ + public function getTo() { + return $this->to; + } + + /** Enables or disables dry mode. */ + public function setDry($dry) { + $this->setBoolean('dry', $dry); + } +} diff --git a/sources/lib/Log4php/appenders/LoggerAppenderMongoDB.php b/sources/lib/Log4php/appenders/LoggerAppenderMongoDB.php new file mode 100644 index 0000000..5caa49b --- /dev/null +++ b/sources/lib/Log4php/appenders/LoggerAppenderMongoDB.php @@ -0,0 +1,360 @@ +host = self::DEFAULT_MONGO_URL_PREFIX . self::DEFAULT_MONGO_HOST; + $this->port = self::DEFAULT_MONGO_PORT; + $this->databaseName = self::DEFAULT_DB_NAME; + $this->collectionName = self::DEFAULT_COLLECTION_NAME; + $this->timeout = self::DEFAULT_TIMEOUT_VALUE; + $this->requiresLayout = false; + } + + /** + * Setup db connection. + * Based on defined options, this method connects to the database and + * creates a {@link $collection}. + */ + public function activateOptions() { + try { + $this->connection = new Mongo(sprintf('%s:%d', $this->host, $this->port), array('timeout' => $this->timeout)); + $db = $this->connection->selectDB($this->databaseName); + if ($this->userName !== null && $this->password !== null) { + $authResult = $db->authenticate($this->userName, $this->password); + if ($authResult['ok'] == floatval(0)) { + throw new Exception($authResult['errmsg'], $authResult['ok']); + } + } + $this->collection = $db->selectCollection($this->collectionName); + } catch (MongoConnectionException $ex) { + $this->closed = true; + $this->warn(sprintf('Failed to connect to mongo deamon: %s', $ex->getMessage())); + } catch (InvalidArgumentException $ex) { + $this->closed = true; + $this->warn(sprintf('Error while selecting mongo database: %s', $ex->getMessage())); + } catch (Exception $ex) { + $this->closed = true; + $this->warn('Invalid credentials for mongo database authentication'); + } + } + + /** + * Appends a new event to the mongo database. + * + * @param LoggerLoggingEvent $event + */ + public function append(LoggerLoggingEvent $event) { + try { + if ($this->collection != null) { + $this->collection->insert($this->format($event)); + } + } catch (MongoCursorException $ex) { + $this->warn(sprintf('Error while writing to mongo collection: %s', $ex->getMessage())); + } + } + + /** + * Converts the logging event into an array which can be logged to mongodb. + * + * @param LoggerLoggingEvent $event + * @return array The array representation of the logging event. + */ + protected function format(LoggerLoggingEvent $event) { + $timestampSec = (int) $event->getTimestamp(); + $timestampUsec = (int) (($event->getTimestamp() - $timestampSec) * 1000000); + + $document = array( + 'timestamp' => new MongoDate($timestampSec, $timestampUsec), + 'level' => $event->getLevel()->toString(), + 'thread' => (int) $event->getThreadName(), + 'message' => $event->getMessage(), + 'loggerName' => $event->getLoggerName() + ); + + $locationInfo = $event->getLocationInformation(); + if ($locationInfo != null) { + $document['fileName'] = $locationInfo->getFileName(); + $document['method'] = $locationInfo->getMethodName(); + $document['lineNumber'] = ($locationInfo->getLineNumber() == 'NA') ? 'NA' : (int) $locationInfo->getLineNumber(); + $document['className'] = $locationInfo->getClassName(); + } + + $throwableInfo = $event->getThrowableInformation(); + if ($throwableInfo != null) { + $document['exception'] = $this->formatThrowable($throwableInfo->getThrowable()); + } + + return $document; + } + + /** + * Converts an Exception into an array which can be logged to mongodb. + * + * Supports innner exceptions (PHP >= 5.3) + * + * @param Exception $ex + * @return array + */ + protected function formatThrowable(Exception $ex) { + $array = array( + 'message' => $ex->getMessage(), + 'code' => $ex->getCode(), + 'stackTrace' => $ex->getTraceAsString(), + ); + + if (method_exists($ex, 'getPrevious') && $ex->getPrevious() !== null) { + $array['innerException'] = $this->formatThrowable($ex->getPrevious()); + } + + return $array; + } + + /** + * Closes the connection to the logging database + */ + public function close() { + if($this->closed != true) { + $this->collection = null; + if ($this->connection !== null) { + $this->connection->close(); + $this->connection = null; + } + $this->closed = true; + } + } + + /** + * Sets the value of {@link $host} parameter. + * @param string $host + */ + public function setHost($host) { + if (!preg_match('/^mongodb\:\/\//', $host)) { + $host = self::DEFAULT_MONGO_URL_PREFIX . $host; + } + $this->host = $host; + } + + /** + * Returns the value of {@link $host} parameter. + * @return string + */ + public function getHost() { + return $this->host; + } + + /** + * Sets the value of {@link $port} parameter. + * @param int $port + */ + public function setPort($port) { + $this->setPositiveInteger('port', $port); + } + + /** + * Returns the value of {@link $port} parameter. + * @return int + */ + public function getPort() { + return $this->port; + } + + /** + * Sets the value of {@link $databaseName} parameter. + * @param string $databaseName + */ + public function setDatabaseName($databaseName) { + $this->setString('databaseName', $databaseName); + } + + /** + * Returns the value of {@link $databaseName} parameter. + * @return string + */ + public function getDatabaseName() { + return $this->databaseName; + } + + /** + * Sets the value of {@link $collectionName} parameter. + * @param string $collectionName + */ + public function setCollectionName($collectionName) { + $this->setString('collectionName', $collectionName); + } + + /** + * Returns the value of {@link $collectionName} parameter. + * @return string + */ + public function getCollectionName() { + return $this->collectionName; + } + + /** + * Sets the value of {@link $userName} parameter. + * @param string $userName + */ + public function setUserName($userName) { + $this->setString('userName', $userName, true); + } + + /** + * Returns the value of {@link $userName} parameter. + * @return string + */ + public function getUserName() { + return $this->userName; + } + + /** + * Sets the value of {@link $password} parameter. + * @param string $password + */ + public function setPassword($password) { + $this->setString('password', $password, true); + } + + /** + * Returns the value of {@link $password} parameter. + * @return string + */ + public function getPassword() { + return $this->password; + } + + /** + * Sets the value of {@link $timeout} parameter. + * @param int $timeout + */ + public function setTimeout($timeout) { + $this->setPositiveInteger('timeout', $timeout); + } + + /** + * Returns the value of {@link $timeout} parameter. + * @return int + */ + public function getTimeout() { + return $this->timeout; + } + /** + * Returns the mongodb connection. + * @return Mongo + */ + public function getConnection() { + return $this->connection; + } + + /** + * Returns the active mongodb collection. + * @return MongoCollection + */ + public function getCollection() { + return $this->collection; + } +} diff --git a/sources/lib/Log4php/appenders/LoggerAppenderNull.php b/sources/lib/Log4php/appenders/LoggerAppenderNull.php new file mode 100644 index 0000000..e4b1b56 --- /dev/null +++ b/sources/lib/Log4php/appenders/LoggerAppenderNull.php @@ -0,0 +1,44 @@ +establishConnection(); + } catch (PDOException $e) { + $this->warn("Failed connecting to database. Closing appender. Error: " . $e->getMessage()); + $this->close(); + return; + } + + // Parse the insert patterns; pattern parts are comma delimited + $pieces = explode(',', $this->insertPattern); + $converterMap = LoggerLayoutPattern::getDefaultConverterMap(); + foreach($pieces as $pattern) { + $parser = new LoggerPatternParser($pattern, $converterMap); + $this->converters[] = $parser->parse(); + } + + $this->closed = false; + } + + /** + * Connects to the database, and prepares the insert query. + * @throws PDOException If connect or prepare fails. + */ + protected function establishConnection() { + // Acquire database connection + $this->db = new PDO($this->dsn, $this->user, $this->password); + $this->db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + + // Prepare the insert statement + $insertSQL = str_replace('__TABLE__', $this->table, $this->insertSQL); + $this->preparedInsert = $this->db->prepare($insertSQL); + } + + /** + * Appends a new event to the database. + * + * If writing to database fails, it will retry by re-establishing the + * connection up to $reconnectAttempts times. If writing still fails, + * the appender will close. + */ + public function append(LoggerLoggingEvent $event) { + + for ($attempt = 1; $attempt <= $this->reconnectAttempts + 1; $attempt++) { + try { + // Attempt to write to database + $this->preparedInsert->execute($this->format($event)); + $this->preparedInsert->closeCursor(); + break; + } catch (PDOException $e) { + $this->warn("Failed writing to database: ". $e->getMessage()); + + // Close the appender if it's the last attempt + if ($attempt > $this->reconnectAttempts) { + $this->warn("Failed writing to database after {$this->reconnectAttempts} reconnect attempts. Closing appender."); + $this->close(); + // Otherwise reconnect and try to write again + } else { + $this->warn("Attempting a reconnect (attempt $attempt of {$this->reconnectAttempts})."); + $this->establishConnection(); + } + } + } + } + + /** + * Converts the logging event to a series of database parameters by using + * the converter chain which was set up on activation. + */ + protected function format(LoggerLoggingEvent $event) { + $params = array(); + foreach($this->converters as $converter) { + $buffer = ''; + while ($converter !== null) { + $converter->format($buffer, $event); + $converter = $converter->next; + } + $params[] = $buffer; + } + return $params; + } + + /** + * Closes the connection to the logging database + */ + public function close() { + // Close the connection (if any) + $this->db = null; + + // Close the appender + $this->closed = true; + } + + // ****************************************** + // *** Accessor methods *** + // ****************************************** + + /** + * Returns the active database handle or null if not established. + * @return PDO + */ + public function getDatabaseHandle() { + return $this->db; + } + + /** Sets the username. */ + public function setUser($user) { + $this->setString('user', $user); + } + + /** Returns the username. */ + public function getUser($user) { + return $this->user; + } + + /** Sets the password. */ + public function setPassword($password) { + $this->setString('password', $password); + } + + /** Returns the password. */ + public function getPassword($password) { + return $this->password; + } + + /** Sets the insert SQL. */ + public function setInsertSQL($sql) { + $this->setString('insertSQL', $sql); + } + + /** Returns the insert SQL. */ + public function getInsertSQL($sql) { + return $this->insertSQL; + } + + /** Sets the insert pattern. */ + public function setInsertPattern($pattern) { + $this->setString('insertPattern', $pattern); + } + + /** Returns the insert pattern. */ + public function getInsertPattern($pattern) { + return $this->insertPattern; + } + + /** Sets the table name. */ + public function setTable($table) { + $this->setString('table', $table); + } + + /** Returns the table name. */ + public function getTable($table) { + return $this->table; + } + + /** Sets the DSN string. */ + public function setDSN($dsn) { + $this->setString('dsn', $dsn); + } + + /** Returns the DSN string. */ + public function getDSN($dsn) { + return $this->setString('dsn', $dsn); + } +} diff --git a/sources/lib/Log4php/appenders/LoggerAppenderPhp.php b/sources/lib/Log4php/appenders/LoggerAppenderPhp.php new file mode 100644 index 0000000..f1d8cef --- /dev/null +++ b/sources/lib/Log4php/appenders/LoggerAppenderPhp.php @@ -0,0 +1,49 @@ +level < WARN mapped to E_USER_NOTICE + * - WARN <= level < ERROR mapped to E_USER_WARNING + * - level >= ERROR mapped to E_USER_ERROR + * + * @version $Revision: 1337820 $ + * @package log4php + * @subpackage appenders + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0 + * @link http://logging.apache.org/log4php/docs/appenders/php.html Appender documentation + */ +class LoggerAppenderPhp extends LoggerAppender { + + public function append(LoggerLoggingEvent $event) { + $level = $event->getLevel(); + if($level->isGreaterOrEqual(LoggerLevel::getLevelError())) { + trigger_error($this->layout->format($event), E_USER_ERROR); + } else if ($level->isGreaterOrEqual(LoggerLevel::getLevelWarn())) { + trigger_error($this->layout->format($event), E_USER_WARNING); + } else { + trigger_error($this->layout->format($event), E_USER_NOTICE); + } + } +} diff --git a/sources/lib/Log4php/appenders/LoggerAppenderRollingFile.php b/sources/lib/Log4php/appenders/LoggerAppenderRollingFile.php new file mode 100644 index 0000000..dbd69de --- /dev/null +++ b/sources/lib/Log4php/appenders/LoggerAppenderRollingFile.php @@ -0,0 +1,305 @@ +maxFileSize
    . + * + * There is one backup file by default. + * + * @var integer + */ + protected $maxBackupIndex = 1; + + /** + * The compress parameter determindes the compression with zlib. + * If set to true, the rollover files are compressed and saved with the .gz extension. + * @var boolean + */ + protected $compress = false; + + /** + * Set to true in the constructor if PHP >= 5.3.0. In that case clearstatcache + * supports conditional clearing of statistics. + * @var boolean + * @see http://php.net/manual/en/function.clearstatcache.php + */ + private $clearConditional = false; + + /** + * Get the maximum size that the output file is allowed to reach + * before being rolled over to backup files. + * @return integer + */ + public function getMaximumFileSize() { + return $this->maxFileSize; + } + + public function __construct($name = '') { + parent::__construct($name); + if (version_compare(PHP_VERSION, '5.3.0') >= 0) { + $this->clearConditional = true; + } + } + + /** + * Implements the usual roll over behaviour. + * + * If MaxBackupIndex is positive, then files File.1, ..., File.MaxBackupIndex -1 are renamed to File.2, ..., File.MaxBackupIndex. + * Moreover, File is renamed File.1 and closed. A new File is created to receive further log output. + * + * If MaxBackupIndex is equal to zero, then the File is truncated with no backup files created. + * + * Rollover must be called while the file is locked so that it is safe for concurrent access. + * + * @throws LoggerException If any part of the rollover procedure fails. + */ + private function rollOver() { + // If maxBackups <= 0, then there is no file renaming to be done. + if($this->maxBackupIndex > 0) { + // Delete the oldest file, to keep Windows happy. + $file = $this->file . '.' . $this->maxBackupIndex; + + if (file_exists($file) && !unlink($file)) { + throw new LoggerException("Unable to delete oldest backup file from [$file]."); + } + + // Map {(maxBackupIndex - 1), ..., 2, 1} to {maxBackupIndex, ..., 3, 2} + $this->renameArchievedLogs($this->file); + + // Backup the active file + $this->moveToBackup($this->file); + } + + // Truncate the active file + ftruncate($this->fp, 0); + rewind($this->fp); + } + + private function moveToBackup($source) { + if ($this->compress) { + $target = $source . '.1.gz'; + $this->compressFile($source, $target); + } else { + $target = $source . '.1'; + copy($source, $target); + } + } + + private function compressFile($source, $target) { + $target = 'compress.zlib://' . $target; + + $fin = fopen($source, 'rb'); + if ($fin === false) { + throw new LoggerException("Unable to open file for reading: [$source]."); + } + + $fout = fopen($target, 'wb'); + if ($fout === false) { + throw new LoggerException("Unable to open file for writing: [$target]."); + } + + while (!feof($fin)) { + $chunk = fread($fin, self::COMPRESS_CHUNK_SIZE); + if (false === fwrite($fout, $chunk)) { + throw new LoggerException("Failed writing to compressed file."); + } + } + + fclose($fin); + fclose($fout); + } + + private function renameArchievedLogs($fileName) { + for($i = $this->maxBackupIndex - 1; $i >= 1; $i--) { + + $source = $fileName . "." . $i; + if ($this->compress) { + $source .= '.gz'; + } + + if(file_exists($source)) { + $target = $fileName . '.' . ($i + 1); + if ($this->compress) { + $target .= '.gz'; + } + + rename($source, $target); + } + } + } + + /** + * Writes a string to the target file. Opens file if not already open. + * @param string $string Data to write. + */ + protected function write($string) { + // Lazy file open + if(!isset($this->fp)) { + if ($this->openFile() === false) { + return; // Do not write if file open failed. + } + } + + // Lock the file while writing and possible rolling over + if(flock($this->fp, LOCK_EX)) { + + // Write to locked file + if(fwrite($this->fp, $string) === false) { + $this->warn("Failed writing to file. Closing appender."); + $this->closed = true; + } + + // Stats cache must be cleared, otherwise filesize() returns cached results + // If supported (PHP 5.3+), clear only the state cache for the target file + if ($this->clearConditional) { + clearstatcache(true, $this->file); + } else { + clearstatcache(); + } + + // Rollover if needed + if (filesize($this->file) > $this->maxFileSize) { + try { + $this->rollOver(); + } catch (LoggerException $ex) { + $this->warn("Rollover failed: " . $ex->getMessage() . " Closing appender."); + $this->closed = true; + } + } + + flock($this->fp, LOCK_UN); + } else { + $this->warn("Failed locking file for writing. Closing appender."); + $this->closed = true; + } + } + + public function activateOptions() { + parent::activateOptions(); + + if ($this->compress && !extension_loaded('zlib')) { + $this->warn("The 'zlib' extension is required for file compression. Disabling compression."); + $this->compression = false; + } + } + + /** + * Set the 'maxBackupIndex' parameter. + * @param integer $maxBackupIndex + */ + public function setMaxBackupIndex($maxBackupIndex) { + $this->setPositiveInteger('maxBackupIndex', $maxBackupIndex); + } + + /** + * Returns the 'maxBackupIndex' parameter. + * @return integer + */ + public function getMaxBackupIndex() { + return $this->maxBackupIndex; + } + + /** + * Set the 'maxFileSize' parameter. + * @param mixed $maxFileSize + */ + public function setMaxFileSize($maxFileSize) { + $this->setFileSize('maxFileSize', $maxFileSize); + } + + /** + * Returns the 'maxFileSize' parameter. + * @return integer + */ + public function getMaxFileSize() { + return $this->maxFileSize; + } + + /** + * Set the 'maxFileSize' parameter (kept for backward compatibility). + * @param mixed $maxFileSize + * @deprecated Use setMaxFileSize() instead. + */ + public function setMaximumFileSize($maxFileSize) { + $this->warn("The 'maximumFileSize' parameter is deprecated. Use 'maxFileSize' instead."); + return $this->setMaxFileSize($maxFileSize); + } + + /** + * Sets the 'compress' parameter. + * @param boolean $compress + */ + public function setCompress($compress) { + $this->setBoolean('compress', $compress); + } + + /** + * Returns the 'compress' parameter. + * @param boolean + */ + public function getCompress() { + return $this->compress; + } +} diff --git a/sources/lib/Log4php/appenders/LoggerAppenderSocket.php b/sources/lib/Log4php/appenders/LoggerAppenderSocket.php new file mode 100644 index 0000000..f6c2a60 --- /dev/null +++ b/sources/lib/Log4php/appenders/LoggerAppenderSocket.php @@ -0,0 +1,122 @@ +remoteHost)) { + $this->warn("Required parameter [remoteHost] not set. Closing appender."); + $this->closed = true; + return; + } + + if (empty($this->timeout)) { + $this->timeout = ini_get("default_socket_timeout"); + } + + $this->closed = false; + } + + public function append(LoggerLoggingEvent $event) { + $socket = fsockopen($this->remoteHost, $this->port, $errno, $errstr, $this->timeout); + if ($socket === false) { + $this->warn("Could not open socket to {$this->remoteHost}:{$this->port}. Closing appender."); + $this->closed = true; + return; + } + + if (false === fwrite($socket, $this->layout->format($event))) { + $this->warn("Error writing to socket. Closing appender."); + $this->closed = true; + } + fclose($socket); + } + + // ****************************************** + // *** Accessor methods *** + // ****************************************** + + /** Sets the target host. */ + public function setRemoteHost($hostname) { + $this->setString('remoteHost', $hostname); + } + + /** Sets the target port */ + public function setPort($port) { + $this->setPositiveInteger('port', $port); + } + + /** Sets the timeout. */ + public function setTimeout($timeout) { + $this->setPositiveInteger('timeout', $timeout); + } + + /** Returns the target host. */ + public function getRemoteHost() { + return $this->getRemoteHost(); + } + + /** Returns the target port. */ + public function getPort() { + return $this->port; + } + + /** Returns the timeout */ + public function getTimeout() { + return $this->timeout; + } +} diff --git a/sources/lib/Log4php/appenders/LoggerAppenderSyslog.php b/sources/lib/Log4php/appenders/LoggerAppenderSyslog.php new file mode 100644 index 0000000..2a7a26e --- /dev/null +++ b/sources/lib/Log4php/appenders/LoggerAppenderSyslog.php @@ -0,0 +1,303 @@ +FATAL to LOG_ALERT + * - ERROR to LOG_ERR + * - WARN to LOG_WARNING + * - INFO to LOG_INFO + * - DEBUG to LOG_DEBUG + * - TRACE to LOG_DEBUG + * + * @version $Revision: 1337820 $ + * @package log4php + * @subpackage appenders + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0 + * @link http://logging.apache.org/log4php/docs/appenders/syslog.html Appender documentation + */ +class LoggerAppenderSyslog extends LoggerAppender { + + /** + * The ident string is added to each message. Typically the name of your application. + * + * @var string + */ + protected $ident = "Apache log4php"; + + /** + * The syslog priority to use when overriding priority. This setting is + * required if {@link overridePriority} is set to true. + * + * @var string + */ + protected $priority; + + /** + * The option used when opening the syslog connection. + * + * @var string + */ + protected $option = 'PID|CONS'; + + /** + * The facility value indicates the source of the message. + * + * @var string + */ + protected $facility = 'USER'; + + /** + * If set to true, the message priority will always use the value defined + * in {@link $priority}, otherwise the priority will be determined by the + * message's log level. + * + * @var string + */ + protected $overridePriority = false; + + /** + * Holds the int value of the {@link $priority}. + * @var int + */ + private $intPriority; + + /** + * Holds the int value of the {@link $facility}. + * @var int + */ + private $intFacility; + + /** + * Holds the int value of the {@link $option}. + * @var int + */ + private $intOption; + + /** + * Sets the {@link $ident}. + * + * @param string $ident + */ + public function setIdent($ident) { + $this->ident = $ident; + } + + /** + * Sets the {@link $priority}. + * + * @param string $priority + */ + public function setPriority($priority) { + $this->priority = $priority; + } + + /** + * Sets the {@link $facility}. + * + * @param string $facility + */ + public function setFacility($facility) { + $this->facility = $facility; + } + + /** + * Sets the {@link $overridePriority}. + * + * @param string $overridePriority + */ + public function setOverridePriority($overridePriority) { + $this->overridePriority = $overridePriority; + } + + /** + * Sets the 'option' parameter. + * + * @param string $option + */ + public function setOption($option) { + $this->option = $option; + } + + /** + * Returns the 'ident' parameter. + * + * @return string $ident + */ + public function getIdent() { + return $this->ident; + } + + /** + * Returns the 'priority' parameter. + * + * @return string + */ + public function getPriority() { + return $this->priority; + } + + /** + * Returns the 'facility' parameter. + * + * @return string + */ + public function getFacility() { + return $this->facility; + } + + /** + * Returns the 'overridePriority' parameter. + * + * @return string + */ + public function getOverridePriority() { + return $this->overridePriority; + } + + /** + * Returns the 'option' parameter. + * + * @return string + */ + public function getOption() { + return $this->option; + } + + + public function activateOptions() { + $this->intPriority = $this->parsePriority(); + $this->intOption = $this->parseOption(); + $this->intFacility = $this->parseFacility(); + + $this->closed = false; + } + + public function close() { + if($this->closed != true) { + closelog(); + $this->closed = true; + } + } + + /** + * Appends the event to syslog. + * + * Log is opened and closed each time because if it is not closed, it + * can cause the Apache httpd server to log to whatever ident/facility + * was used in openlog(). + * + * @see http://www.php.net/manual/en/function.syslog.php#97843 + */ + public function append(LoggerLoggingEvent $event) { + $priority = $this->getSyslogPriority($event->getLevel()); + $message = $this->layout->format($event); + + openlog($this->ident, $this->intOption, $this->intFacility); + syslog($priority, $message); + closelog(); + } + + /** Determines which syslog priority to use based on the given level. */ + private function getSyslogPriority(LoggerLevel $level) { + if($this->overridePriority) { + return $this->intPriority; + } + return $level->getSyslogEquivalent(); + } + + /** Parses a syslog option string and returns the correspodning int value. */ + private function parseOption() { + $value = 0; + $options = explode('|', $this->option); + + foreach($options as $option) { + if (!empty($option)) { + $constant = "LOG_" . trim($option); + if (defined($constant)) { + $value |= constant($constant); + } else { + trigger_error("log4php: Invalid syslog option provided: $option. Whole option string: {$this->option}.", E_USER_WARNING); + } + } + } + return $value; + } + + /** Parses the facility string and returns the corresponding int value. */ + private function parseFacility() { + if (!empty($this->facility)) { + $constant = "LOG_" . trim($this->facility); + if (defined($constant)) { + return constant($constant); + } else { + trigger_error("log4php: Invalid syslog facility provided: {$this->facility}.", E_USER_WARNING); + } + } + } + + /** Parses the priority string and returns the corresponding int value. */ + private function parsePriority() { + if (!empty($this->priority)) { + $constant = "LOG_" . trim($this->priority); + if (defined($constant)) { + return constant($constant); + } else { + trigger_error("log4php: Invalid syslog priority provided: {$this->priority}.", E_USER_WARNING); + } + } + } +} diff --git a/sources/lib/Log4php/configurators/LoggerConfigurationAdapter.php b/sources/lib/Log4php/configurators/LoggerConfigurationAdapter.php new file mode 100644 index 0000000..17871fc --- /dev/null +++ b/sources/lib/Log4php/configurators/LoggerConfigurationAdapter.php @@ -0,0 +1,39 @@ +load($path); + + // Parse threshold + if (isset($properties[self::THRESHOLD_PREFIX])) { + $this->config['threshold'] = $properties[self::THRESHOLD_PREFIX]; + } + + // Parse root logger + if (isset($properties[self::ROOT_LOGGER_PREFIX])) { + $this->parseLogger($properties[self::ROOT_LOGGER_PREFIX], self::ROOT_LOGGER_NAME); + } + + $appenders = array(); + + foreach($properties as $key => $value) { + // Parse loggers + if ($this->beginsWith($key, self::LOGGER_PREFIX)) { + $name = substr($key, strlen(self::LOGGER_PREFIX)); + $this->parseLogger($value, $name); + } + + // Parse additivity + if ($this->beginsWith($key, self::ADDITIVITY_PREFIX)) { + $name = substr($key, strlen(self::ADDITIVITY_PREFIX)); + $this->config['loggers'][$name]['additivity'] = $value; + } + + // Parse appenders + else if ($this->beginsWith($key, self::APPENDER_PREFIX)) { + $this->parseAppender($key, $value); + } + + // Parse renderers + else if ($this->beginsWith($key, self::RENDERER_PREFIX)) { + $this->parseRenderer($key, $value); + } + } + + return $this->config; + } + + + /** + * Parses a logger definition. + * + * Loggers are defined in the following manner: + *

    +	 * log4php.logger. = [], [, , ...] 
    +	 * 
    + * + * @param string $value The configuration value (level and appender-refs). + * @param string $name Logger name. + */ + private function parseLogger($value, $name) { + // Value is divided by commas + $parts = explode(',', $value); + if (empty($value) || empty($parts)) { + return; + } + + // The first value is the logger level + $level = array_shift($parts); + + // The remaining values are appender references + $appenders = array(); + while($appender = array_shift($parts)) { + $appender = trim($appender); + if (!empty($appender)) { + $appenders[] = trim($appender); + } + } + + // Find the target configuration + if ($name == self::ROOT_LOGGER_NAME) { + $this->config['rootLogger']['level'] = trim($level); + $this->config['rootLogger']['appenders'] = $appenders; + } else { + $this->config['loggers'][$name]['level'] = trim($level); + $this->config['loggers'][$name]['appenders'] = $appenders; + } + } + + /** + * Parses an configuration line pertaining to an appender. + * + * Parses the following patterns: + * + * Appender class: + *
    +	 * log4php.appender. = 
    +	 * 
    + * + * Appender parameter: + *
    +	 * log4php.appender.. = 
    +	 * 
    + * + * Appender threshold: + *
    +	 * log4php.appender..threshold = 
    +	 * 
    + * + * Appender layout: + *
    +	 * log4php.appender..layout = 
    +	 * 
    + * + * Layout parameter: + *
    +	 * log4php.appender..layout. = 
    +	 * 
    + * + * For example, a full appender config might look like: + *
    +	 * log4php.appender.myAppender = LoggerAppenderConsole
    +	 * log4php.appender.myAppender.threshold = info
    +	 * log4php.appender.myAppender.target = stdout
    +	 * log4php.appender.myAppender.layout = LoggerLayoutPattern
    +	 * log4php.appender.myAppender.layout.conversionPattern = "%d %c: %m%n"
    +	 * 
    + * + * After parsing all these options, the following configuration can be + * found under $this->config['appenders']['myAppender']: + *
    +	 * array(
    +	 * 	'class' => LoggerAppenderConsole,
    +	 * 	'threshold' => info,
    +	 * 	'params' => array(
    +	 * 		'target' => 'stdout'
    +	 * 	),
    +	 * 	'layout' => array(
    +	 * 		'class' => 'LoggerAppenderConsole',
    +	 * 		'params' => array(
    +	 * 			'conversionPattern' => '%d %c: %m%n'
    +	 * 		)
    +	 * 	)
    +	 * )
    +	 * 
    + * + * @param string $key + * @param string $value + */ + private function parseAppender($key, $value) { + + // Remove the appender prefix from key + $subKey = substr($key, strlen(self::APPENDER_PREFIX)); + + // Divide the string by dots + $parts = explode('.', $subKey); + $count = count($parts); + + // The first part is always the appender name + $name = trim($parts[0]); + + // Only one part - this line defines the appender class + if ($count == 1) { + $this->config['appenders'][$name]['class'] = $value; + return; + } + + // Two parts - either a parameter, a threshold or layout class + else if ($count == 2) { + + if ($parts[1] == 'layout') { + $this->config['appenders'][$name]['layout']['class'] = $value; + return; + } else if ($parts[1] == 'threshold') { + $this->config['appenders'][$name]['threshold'] = $value; + return; + } else { + $this->config['appenders'][$name]['params'][$parts[1]] = $value; + return; + } + } + + // Three parts - this can only be a layout parameter + else if ($count == 3) { + if ($parts[1] == 'layout') { + $this->config['appenders'][$name]['layout']['params'][$parts[2]] = $value; + return; + } + } + + trigger_error("log4php: Don't know how to parse the following line: \"$key = $value\". Skipping."); + } + + /** + * Parses a renderer definition. + * + * Renderers are defined as: + *
    +	 * log4php.renderer. =  
    +	 * 
    + * + * @param string $key log4php.renderer. + * @param string $value + */ + private function parseRenderer($key, $value) { + // Remove the appender prefix from key + $renderedClass = substr($key, strlen(self::APPENDER_PREFIX)); + $renderingClass = $value; + + $this->config['renderers'][] = compact('renderedClass', 'renderingClass'); + } + + /** Helper method. Returns true if $str begins with $sub. */ + private function beginsWith($str, $sub) { + return (strncmp($str, $sub, strlen($sub)) == 0); + } + + +} + diff --git a/sources/lib/Log4php/configurators/LoggerConfigurationAdapterPHP.php b/sources/lib/Log4php/configurators/LoggerConfigurationAdapterPHP.php new file mode 100644 index 0000000..8d195e9 --- /dev/null +++ b/sources/lib/Log4php/configurators/LoggerConfigurationAdapterPHP.php @@ -0,0 +1,84 @@ + + * array( + * 'level' => 'info', + * 'appenders' => array('default') + * ), + * 'appenders' => array( + * 'default' => array( + * 'class' => 'LoggerAppenderEcho', + * 'layout' => array( + * 'class' => 'LoggerLayoutSimple' + * ) + * ) + * ) + * ) + * ?> + * + * + * @package log4php + * @subpackage configurators + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0 + * @version $Revision: 1343601 $ + * @since 2.2 + */ +class LoggerConfigurationAdapterPHP implements LoggerConfigurationAdapter +{ + public function convert($url) { + if (!file_exists($url)) { + throw new LoggerException("File [$url] does not exist."); + } + + // Load the config file + $data = @file_get_contents($url); + if ($data === false) { + $error = error_get_last(); + throw new LoggerException("Error loading config file: {$error['message']}"); + } + + $config = @eval('?>' . $data); + + if ($config === false) { + $error = error_get_last(); + throw new LoggerException("Error parsing configuration: " . $error['message']); + } + + if (empty($config)) { + throw new LoggerException("Invalid configuration: empty configuration array."); + } + + if (!is_array($config)) { + throw new LoggerException("Invalid configuration: not an array."); + } + + return $config; + } +} + diff --git a/sources/lib/Log4php/configurators/LoggerConfigurationAdapterXML.php b/sources/lib/Log4php/configurators/LoggerConfigurationAdapterXML.php new file mode 100644 index 0000000..4c161aa --- /dev/null +++ b/sources/lib/Log4php/configurators/LoggerConfigurationAdapterXML.php @@ -0,0 +1,278 @@ + array(), + 'loggers' => array(), + 'renderers' => array(), + ); + + public function convert($url) { + $xml = $this->loadXML($url); + + $this->parseConfiguration($xml); + + // Parse the node + if (isset($xml->root)) { + $this->parseRootLogger($xml->root); + } + + // Process nodes + foreach($xml->logger as $logger) { + $this->parseLogger($logger); + } + + // Process nodes + foreach($xml->appender as $appender) { + $this->parseAppender($appender); + } + + // Process nodes + foreach($xml->renderer as $rendererNode) { + $this->parseRenderer($rendererNode); + } + + // Process node + foreach($xml->defaultRenderer as $rendererNode) { + $this->parseDefaultRenderer($rendererNode); + } + + return $this->config; + } + + /** + * Loads and validates the XML. + * @param string $url Input XML. + */ + private function loadXML($url) { + if (!file_exists($url)) { + throw new LoggerException("File [$url] does not exist."); + } + + libxml_clear_errors(); + $oldValue = libxml_use_internal_errors(true); + + // Load XML + $xml = @simplexml_load_file($url); + if ($xml === false) { + + $errorStr = ""; + foreach(libxml_get_errors() as $error) { + $errorStr .= $error->message; + } + + throw new LoggerException("Error loading configuration file: " . trim($errorStr)); + } + + libxml_clear_errors(); + libxml_use_internal_errors($oldValue); + + return $xml; + } + + /** + * Parses the node. + */ + private function parseConfiguration(SimpleXMLElement $xml) { + $attributes = $xml->attributes(); + if (isset($attributes['threshold'])) { + $this->config['threshold'] = (string) $attributes['threshold']; + } + } + + /** Parses an node. */ + private function parseAppender(SimpleXMLElement $node) { + $name = $this->getAttributeValue($node, 'name'); + if (empty($name)) { + $this->warn("An node is missing the required 'name' attribute. Skipping appender definition."); + return; + } + + $appender = array(); + $appender['class'] = $this->getAttributeValue($node, 'class'); + + if (isset($node['threshold'])) { + $appender['threshold'] = $this->getAttributeValue($node, 'threshold'); + } + + if (isset($node->layout)) { + $appender['layout']= $this->parseLayout($node->layout, $name); + } + + if (count($node->param) > 0) { + $appender['params'] = $this->parseParameters($node); + } + + foreach($node->filter as $filterNode) { + $appender['filters'][] = $this->parseFilter($filterNode); + } + + $this->config['appenders'][$name] = $appender; + } + + /** Parses a node. */ + private function parseLayout(SimpleXMLElement $node, $appenderName) { + $layout = array(); + $layout['class'] = $this->getAttributeValue($node, 'class'); + + if (count($node->param) > 0) { + $layout['params'] = $this->parseParameters($node); + } + + return $layout; + } + /** Parses any child nodes returning them in an array. */ + private function parseParameters($paramsNode) { + $params = array(); + + foreach($paramsNode->param as $paramNode) { + if (empty($paramNode['name'])) { + $this->warn("A node is missing the required 'name' attribute. Skipping parameter."); + continue; + } + + $name = $this->getAttributeValue($paramNode, 'name'); + $value = $this->getAttributeValue($paramNode, 'value'); + + $params[$name] = $value; + } + + return $params; + } + + /** Parses a node. */ + private function parseRootLogger(SimpleXMLElement $node) { + $logger = array(); + + if (isset($node->level)) { + $logger['level'] = $this->getAttributeValue($node->level, 'value'); + } + + $logger['appenders'] = $this->parseAppenderReferences($node); + + $this->config['rootLogger'] = $logger; + } + + /** Parses a node. */ + private function parseLogger(SimpleXMLElement $node) { + $logger = array(); + + $name = $this->getAttributeValue($node, 'name'); + if (empty($name)) { + $this->warn("A node is missing the required 'name' attribute. Skipping logger definition."); + return; + } + + if (isset($node->level)) { + $logger['level'] = $this->getAttributeValue($node->level, 'value'); + } + + if (isset($node['additivity'])) { + $logger['additivity'] = $this->getAttributeValue($node, 'additivity'); + } + + $logger['appenders'] = $this->parseAppenderReferences($node); + + // Check for duplicate loggers + if (isset($this->config['loggers'][$name])) { + $this->warn("Duplicate logger definition [$name]. Overwriting."); + } + + $this->config['loggers'][$name] = $logger; + } + + /** + * Parses a node for appender references and returns them in an array. + * + * Previous versions supported appender-ref, as well as appender_ref so both + * are parsed for backward compatibility. + */ + private function parseAppenderReferences(SimpleXMLElement $node) { + $refs = array(); + foreach($node->appender_ref as $ref) { + $refs[] = $this->getAttributeValue($ref, 'ref'); + } + + foreach($node->{'appender-ref'} as $ref) { + $refs[] = $this->getAttributeValue($ref, 'ref'); + } + + return $refs; + } + + /** Parses a node. */ + private function parseFilter($filterNode) { + $filter = array(); + $filter['class'] = $this->getAttributeValue($filterNode, 'class'); + + if (count($filterNode->param) > 0) { + $filter['params'] = $this->parseParameters($filterNode); + } + + return $filter; + } + + /** Parses a node. */ + private function parseRenderer(SimpleXMLElement $node) { + $renderedClass = $this->getAttributeValue($node, 'renderedClass'); + $renderingClass = $this->getAttributeValue($node, 'renderingClass'); + + $this->config['renderers'][] = compact('renderedClass', 'renderingClass'); + } + + /** Parses a node. */ + private function parseDefaultRenderer(SimpleXMLElement $node) { + $renderingClass = $this->getAttributeValue($node, 'renderingClass'); + + // Warn on duplicates + if(isset($this->config['defaultRenderer'])) { + $this->warn("Duplicate node. Overwriting."); + } + + $this->config['defaultRenderer'] = $renderingClass; + } + + // ****************************************** + // ** Helper methods ** + // ****************************************** + + private function getAttributeValue(SimpleXMLElement $node, $name) { + return isset($node[$name]) ? (string) $node[$name] : null; + } + + private function warn($message) { + trigger_error("log4php: " . $message, E_USER_WARNING); + } +} + diff --git a/sources/lib/Log4php/configurators/LoggerConfiguratorDefault.php b/sources/lib/Log4php/configurators/LoggerConfiguratorDefault.php new file mode 100644 index 0000000..ac360bb --- /dev/null +++ b/sources/lib/Log4php/configurators/LoggerConfiguratorDefault.php @@ -0,0 +1,477 @@ + 'LoggerConfigurationAdapterXML', + self::FORMAT_INI => 'LoggerConfigurationAdapterINI', + self::FORMAT_PHP => 'LoggerConfigurationAdapterPHP', + ); + + /** Default configuration; used if no configuration file is provided. */ + private static $defaultConfiguration = array( + 'threshold' => 'ALL', + 'rootLogger' => array( + 'level' => 'DEBUG', + 'appenders' => array('default'), + ), + 'appenders' => array( + 'default' => array( + 'class' => 'LoggerAppenderEcho' + ), + ), + ); + + /** Holds the appenders before they are linked to loggers. */ + private $appenders = array(); + + /** + * Configures log4php based on the given configuration. The input can + * either be a path to the config file, or a PHP array holding the + * configuration. + * + * If no configuration is given, or if the given configuration cannot be + * parsed for whatever reason, a warning will be issued, and log4php + * will use the default configuration contained in + * {@link $defaultConfiguration}. + * + * @param LoggerHierarchy $hierarchy The hierarchy on which to perform + * the configuration. + * @param string|array $input Either path to the config file or the + * configuration as an array. If not set, default configuration + * will be used. + */ + public function configure(LoggerHierarchy $hierarchy, $input = null) { + $config = $this->parse($input); + $this->doConfigure($hierarchy, $config); + } + + /** + * Parses the given configuration and returns the parsed configuration + * as a PHP array. Does not perform any configuration. + * + * If no configuration is given, or if the given configuration cannot be + * parsed for whatever reason, a warning will be issued, and the default + * configuration will be returned ({@link $defaultConfiguration}). + * + * @param string|array $input Either path to the config file or the + * configuration as an array. If not set, default configuration + * will be used. + * @return array The parsed configuration. + */ + public function parse($input) { + // No input - use default configuration + if (!isset($input)) { + $config = self::$defaultConfiguration; + } + + // Array input - contains configuration within the array + else if (is_array($input)) { + $config = $input; + } + + // String input - contains path to configuration file + else if (is_string($input)) { + try { + $config = $this->parseFile($input); + } catch (LoggerException $e) { + $this->warn("Configuration failed. " . $e->getMessage() . " Using default configuration."); + $config = self::$defaultConfiguration; + } + } + + // Anything else is an error + else { + $this->warn("Invalid configuration param given. Reverting to default configuration."); + $config = self::$defaultConfiguration; + } + + return $config; + } + + /** + * Returns the default log4php configuration. + * @return array + */ + public static function getDefaultConfiguration() { + return self::$defaultConfiguration; + } + + /** + * Loads the configuration file from the given URL, determines which + * adapter to use, converts the configuration to a PHP array and + * returns it. + * + * @param string $url Path to the config file. + * @return The configuration from the config file, as a PHP array. + * @throws LoggerException If the configuration file cannot be loaded, or + * if the parsing fails. + */ + private function parseFile($url) { + + if (!file_exists($url)) { + throw new LoggerException("File not found at [$url]."); + } + + $type = $this->getConfigType($url); + $adapterClass = $this->adapters[$type]; + + $adapter = new $adapterClass(); + return $adapter->convert($url); + } + + /** Determines configuration file type based on the file extension. */ + private function getConfigType($url) { + $info = pathinfo($url); + $ext = strtolower($info['extension']); + + switch($ext) { + case 'xml': + return self::FORMAT_XML; + + case 'ini': + case 'properties': + return self::FORMAT_INI; + + case 'php': + return self::FORMAT_PHP; + + default: + throw new LoggerException("Unsupported configuration file extension: $ext"); + } + } + + /** + * Constructs the logger hierarchy based on configuration. + * + * @param LoggerHierarchy $hierarchy + * @param array $config + */ + private function doConfigure(LoggerHierarchy $hierarchy, $config) { + if (isset($config['threshold'])) { + $threshold = LoggerLevel::toLevel($config['threshold']); + if (isset($threshold)) { + $hierarchy->setThreshold($threshold); + } else { + $this->warn("Invalid threshold value [{$config['threshold']}] specified. Ignoring threshold definition."); + } + } + + // Configure appenders and add them to the appender pool + if (isset($config['appenders']) && is_array($config['appenders'])) { + foreach($config['appenders'] as $name => $appenderConfig) { + $this->configureAppender($name, $appenderConfig); + } + } + + // Configure root logger + if (isset($config['rootLogger'])) { + $this->configureRootLogger($hierarchy, $config['rootLogger']); + } + + // Configure loggers + if (isset($config['loggers']) && is_array($config['loggers'])) { + foreach($config['loggers'] as $loggerName => $loggerConfig) { + $this->configureOtherLogger($hierarchy, $loggerName, $loggerConfig); + } + } + + // Configure renderers + if (isset($config['renderers']) && is_array($config['renderers'])) { + foreach($config['renderers'] as $rendererConfig) { + $this->configureRenderer($hierarchy, $rendererConfig); + } + } + + if (isset($config['defaultRenderer'])) { + $this->configureDefaultRenderer($hierarchy, $config['defaultRenderer']); + } + } + + private function configureRenderer(LoggerHierarchy $hierarchy, $config) { + if (empty($config['renderingClass'])) { + $this->warn("Rendering class not specified. Skipping renderer definition."); + return; + } + + if (empty($config['renderedClass'])) { + $this->warn("Rendered class not specified. Skipping renderer definition."); + return; + } + + // Error handling performed by RendererMap + $hierarchy->getRendererMap()->addRenderer($config['renderedClass'], $config['renderingClass']); + } + + private function configureDefaultRenderer(LoggerHierarchy $hierarchy, $class) { + if (empty($class)) { + $this->warn("Rendering class not specified. Skipping default renderer definition."); + return; + } + + // Error handling performed by RendererMap + $hierarchy->getRendererMap()->setDefaultRenderer($class); + } + + /** + * Configures an appender based on given config and saves it to + * {@link $appenders} array so it can be later linked to loggers. + * @param string $name Appender name. + * @param array $config Appender configuration options. + */ + private function configureAppender($name, $config) { + + // TODO: add this check to other places where it might be useful + if (!is_array($config)) { + $type = gettype($config); + $this->warn("Invalid configuration provided for appender [$name]. Expected an array, found <$type>. Skipping appender definition."); + return; + } + + // Parse appender class + $class = $config['class']; + if (empty($class)) { + $this->warn("No class given for appender [$name]. Skipping appender definition."); + return; + } + if (!class_exists($class)) { + $this->warn("Invalid class [$class] given for appender [$name]. Class does not exist. Skipping appender definition."); + return; + } + + // Instantiate the appender + $appender = new $class($name); + if (!($appender instanceof LoggerAppender)) { + $this->warn("Invalid class [$class] given for appender [$name]. Not a valid LoggerAppender class. Skipping appender definition."); + return; + } + + // Parse the appender threshold + if (isset($config['threshold'])) { + $threshold = LoggerLevel::toLevel($config['threshold']); + if ($threshold instanceof LoggerLevel) { + $appender->setThreshold($threshold); + } else { + $this->warn("Invalid threshold value [{$config['threshold']}] specified for appender [$name]. Ignoring threshold definition."); + } + } + + // Parse the appender layout + if ($appender->requiresLayout() && isset($config['layout'])) { + $this->createAppenderLayout($appender, $config['layout']); + } + + // Parse filters + if (isset($config['filters']) && is_array($config['filters'])) { + foreach($config['filters'] as $filterConfig) { + $this->createAppenderFilter($appender, $filterConfig); + } + } + + // Set options if any + if (isset($config['params'])) { + $this->setOptions($appender, $config['params']); + } + + // Activate and save for later linking to loggers + $appender->activateOptions(); + $this->appenders[$name] = $appender; + } + + /** + * Parses layout config, creates the layout and links it to the appender. + * @param LoggerAppender $appender + * @param array $config Layout configuration. + */ + private function createAppenderLayout(LoggerAppender $appender, $config) { + $name = $appender->getName(); + $class = $config['class']; + if (empty($class)) { + $this->warn("Layout class not specified for appender [$name]. Reverting to default layout."); + return; + } + if (!class_exists($class)) { + $this->warn("Nonexistant layout class [$class] specified for appender [$name]. Reverting to default layout."); + return; + } + + $layout = new $class(); + if (!($layout instanceof LoggerLayout)) { + $this->warn("Invalid layout class [$class] sepcified for appender [$name]. Reverting to default layout."); + return; + } + + if (isset($config['params'])) { + $this->setOptions($layout, $config['params']); + } + + $layout->activateOptions(); + $appender->setLayout($layout); + } + + /** + * Parses filter config, creates the filter and adds it to the appender's + * filter chain. + * @param LoggerAppender $appender + * @param array $config Filter configuration. + */ + private function createAppenderFilter(LoggerAppender $appender, $config) { + $name = $appender->getName(); + $class = $config['class']; + if (!class_exists($class)) { + $this->warn("Nonexistant filter class [$class] specified on appender [$name]. Skipping filter definition."); + return; + } + + $filter = new $class(); + if (!($filter instanceof LoggerFilter)) { + $this->warn("Invalid filter class [$class] sepcified on appender [$name]. Skipping filter definition."); + return; + } + + if (isset($config['params'])) { + $this->setOptions($filter, $config['params']); + } + + $filter->activateOptions(); + $appender->addFilter($filter); + } + + /** + * Configures the root logger + * @see configureLogger() + */ + private function configureRootLogger(LoggerHierarchy $hierarchy, $config) { + $logger = $hierarchy->getRootLogger(); + $this->configureLogger($logger, $config); + } + + /** + * Configures a logger which is not root. + * @see configureLogger() + */ + private function configureOtherLogger(LoggerHierarchy $hierarchy, $name, $config) { + // Get logger from hierarchy (this creates it if it doesn't already exist) + $logger = $hierarchy->getLogger($name); + $this->configureLogger($logger, $config); + } + + /** + * Configures a logger. + * + * @param Logger $logger The logger to configure + * @param array $config Logger configuration options. + */ + private function configureLogger(Logger $logger, $config) { + $loggerName = $logger->getName(); + + // Set logger level + if (isset($config['level'])) { + $level = LoggerLevel::toLevel($config['level']); + if (isset($level)) { + $logger->setLevel($level); + } else { + $this->warn("Invalid level value [{$config['level']}] specified for logger [$loggerName]. Ignoring level definition."); + } + } + + // Link appenders to logger + if (isset($config['appenders'])) { + foreach($config['appenders'] as $appenderName) { + if (isset($this->appenders[$appenderName])) { + $logger->addAppender($this->appenders[$appenderName]); + } else { + $this->warn("Nonexistnant appender [$appenderName] linked to logger [$loggerName]."); + } + } + } + + // Set logger additivity + if (isset($config['additivity'])) { + try { + $additivity = LoggerOptionConverter::toBooleanEx($config['additivity'], null); + $logger->setAdditivity($additivity); + } catch (Exception $ex) { + $this->warn("Invalid additivity value [{$config['additivity']}] specified for logger [$loggerName]. Ignoring additivity setting."); + } + } + } + + /** + * Helper method which applies given options to an object which has setters + * for these options (such as appenders, layouts, etc.). + * + * For example, if options are: + * + * array( + * 'file' => '/tmp/myfile.log', + * 'append' => true + * ) + * + * + * This method will call: + * + * $object->setFile('/tmp/myfile.log') + * $object->setAppend(true) + * + * + * If required setters do not exist, it will produce a warning. + * + * @param mixed $object The object to configure. + * @param unknown_type $options + */ + private function setOptions($object, $options) { + foreach($options as $name => $value) { + $setter = "set$name"; + if (method_exists($object, $setter)) { + $object->$setter($value); + } else { + $class = get_class($object); + $this->warn("Nonexistant option [$name] specified on [$class]. Skipping."); + } + } + } + + /** Helper method to simplify error reporting. */ + private function warn($message) { + trigger_error("log4php: $message", E_USER_WARNING); + } +} diff --git a/sources/lib/Log4php/filters/LoggerFilterDenyAll.php b/sources/lib/Log4php/filters/LoggerFilterDenyAll.php new file mode 100644 index 0000000..4515478 --- /dev/null +++ b/sources/lib/Log4php/filters/LoggerFilterDenyAll.php @@ -0,0 +1,56 @@ + + * An example for this filter: + * + * {@example ../../examples/php/filter_denyall.php 19} + * + *

    + * The corresponding XML file: + * + * {@example ../../examples/resources/filter_denyall.xml 18} + * + * @version $Revision: 883108 $ + * @package log4php + * @subpackage filters + * @since 0.3 + */ +class LoggerFilterDenyAll extends LoggerFilter { + + /** + * Always returns the integer constant {@link LoggerFilter::DENY} + * regardless of the {@link LoggerLoggingEvent} parameter. + * + * @param LoggerLoggingEvent $event The {@link LoggerLoggingEvent} to filter. + * @return LoggerFilter::DENY Always returns {@link LoggerFilter::DENY} + */ + public function decide(LoggerLoggingEvent $event) { + return LoggerFilter::DENY; + } +} diff --git a/sources/lib/Log4php/filters/LoggerFilterLevelMatch.php b/sources/lib/Log4php/filters/LoggerFilterLevelMatch.php new file mode 100644 index 0000000..8ea456f --- /dev/null +++ b/sources/lib/Log4php/filters/LoggerFilterLevelMatch.php @@ -0,0 +1,100 @@ +The filter admits two options LevelToMatch and + * AcceptOnMatch. If there is an exact match between the value + * of the LevelToMatch option and the level of the + * {@link LoggerLoggingEvent}, then the {@link decide()} method returns + * {@link LoggerFilter::ACCEPT} in case the AcceptOnMatch + * option value is set to true, if it is false then + * {@link LoggerFilter::DENY} is returned. If there is no match, + * {@link LoggerFilter::NEUTRAL} is returned.

    + * + *

    + * An example for this filter: + * + * {@example ../../examples/php/filter_levelmatch.php 19} + * + *

    + * The corresponding XML file: + * + * {@example ../../examples/resources/filter_levelmatch.xml 18} + * + * @version $Revision: 1213283 $ + * @package log4php + * @subpackage filters + * @since 0.6 + */ +class LoggerFilterLevelMatch extends LoggerFilter { + + /** + * Indicates if this event should be accepted or denied on match + * @var boolean + */ + protected $acceptOnMatch = true; + + /** + * The level, when to match + * @var LoggerLevel + */ + protected $levelToMatch; + + /** + * @param boolean $acceptOnMatch + */ + public function setAcceptOnMatch($acceptOnMatch) { + $this->setBoolean('acceptOnMatch', $acceptOnMatch); + } + + /** + * @param string $l the level to match + */ + public function setLevelToMatch($level) { + $this->setLevel('levelToMatch', $level); + } + + /** + * Return the decision of this filter. + * + * Returns {@link LoggerFilter::NEUTRAL} if the LevelToMatch + * option is not set or if there is not match. Otherwise, if there is a + * match, then the returned decision is {@link LoggerFilter::ACCEPT} if the + * AcceptOnMatch property is set to true. The + * returned decision is {@link LoggerFilter::DENY} if the + * AcceptOnMatch property is set to false. + * + * @param LoggerLoggingEvent $event + * @return integer + */ + public function decide(LoggerLoggingEvent $event) { + if($this->levelToMatch === null) { + return LoggerFilter::NEUTRAL; + } + + if($this->levelToMatch->equals($event->getLevel())) { + return $this->acceptOnMatch ? LoggerFilter::ACCEPT : LoggerFilter::DENY; + } else { + return LoggerFilter::NEUTRAL; + } + } +} diff --git a/sources/lib/Log4php/filters/LoggerFilterLevelRange.php b/sources/lib/Log4php/filters/LoggerFilterLevelRange.php new file mode 100644 index 0000000..dbdc4ed --- /dev/null +++ b/sources/lib/Log4php/filters/LoggerFilterLevelRange.php @@ -0,0 +1,138 @@ +The filter admits three options LevelMin, LevelMax + * and AcceptOnMatch.

    + * + *

    If the level of the {@link LoggerLoggingEvent} is not between Min and Max + * (inclusive), then {@link LoggerFilter::DENY} is returned.

    + * + *

    If the Logging event level is within the specified range, then if + * AcceptOnMatch is true, + * {@link LoggerFilter::ACCEPT} is returned, and if + * AcceptOnMatch is false, + * {@link LoggerFilter::NEUTRAL} is returned.

    + * + *

    If LevelMin is not defined, then there is no + * minimum acceptable level (i.e. a level is never rejected for + * being too "low"/unimportant). If LevelMax is not + * defined, then there is no maximum acceptable level (ie a + * level is never rejected for being too "high"/important).

    + * + *

    Refer to the {@link LoggerAppender::setThreshold()} method + * available to all appenders extending {@link LoggerAppender} + * for a more convenient way to filter out events by level.

    + * + *

    + * An example for this filter: + * + * {@example ../../examples/php/filter_levelrange.php 19} + * + *

    + * The corresponding XML file: + * + * {@example ../../examples/resources/filter_levelrange.xml 18} + * + * @author Simon Kitching + * @author based on the org.apache.log4j.varia.LevelRangeFilte Java code by Ceki Gülcü + * + * @version $Revision: 1213283 $ + * @package log4php + * @subpackage filters + * @since 0.6 + */ +class LoggerFilterLevelRange extends LoggerFilter { + + /** + * @var boolean + */ + protected $acceptOnMatch = true; + + /** + * @var LoggerLevel + */ + protected $levelMin; + + /** + * @var LoggerLevel + */ + protected $levelMax; + + /** + * @param boolean $acceptOnMatch + */ + public function setAcceptOnMatch($acceptOnMatch) { + $this->setBoolean('acceptOnMatch', $acceptOnMatch); + } + + /** + * @param string $l the level min to match + */ + public function setLevelMin($level) { + $this->setLevel('levelMin', $level); + } + + /** + * @param string $l the level max to match + */ + public function setLevelMax($level) { + $this->setLevel('levelMax', $level); + } + + /** + * Return the decision of this filter. + * + * @param LoggerLoggingEvent $event + * @return integer + */ + public function decide(LoggerLoggingEvent $event) { + $level = $event->getLevel(); + + if($this->levelMin !== null) { + if($level->isGreaterOrEqual($this->levelMin) == false) { + // level of event is less than minimum + return LoggerFilter::DENY; + } + } + + if($this->levelMax !== null) { + if($level->toInt() > $this->levelMax->toInt()) { + // level of event is greater than maximum + // Alas, there is no Level.isGreater method. and using + // a combo of isGreaterOrEqual && !Equal seems worse than + // checking the int values of the level objects.. + return LoggerFilter::DENY; + } + } + + if($this->acceptOnMatch) { + // this filter set up to bypass later filters and always return + // accept if level in range + return LoggerFilter::ACCEPT; + } else { + // event is ok for this filter; allow later filters to have a look.. + return LoggerFilter::NEUTRAL; + } + } +} diff --git a/sources/lib/Log4php/filters/LoggerFilterStringMatch.php b/sources/lib/Log4php/filters/LoggerFilterStringMatch.php new file mode 100644 index 0000000..3787caa --- /dev/null +++ b/sources/lib/Log4php/filters/LoggerFilterStringMatch.php @@ -0,0 +1,89 @@ +The filter admits two options {@link $stringToMatch} and + * {@link $acceptOnMatch}. If there is a match (using {@link PHP_MANUAL#strpos} + * between the value of the {@link $stringToMatch} option and the message + * of the {@link LoggerLoggingEvent}, + * then the {@link decide()} method returns {@link LoggerFilter::ACCEPT} if + * the AcceptOnMatch option value is true, if it is false then + * {@link LoggerFilter::DENY} is returned. If there is no match, {@link LoggerFilter::NEUTRAL} + * is returned.

    + * + *

    + * An example for this filter: + * + * {@example ../../examples/php/filter_stringmatch.php 19} + * + *

    + * The corresponding XML file: + * + * {@example ../../examples/resources/filter_stringmatch.xml 18} + * + * @version $Revision: 1213283 $ + * @package log4php + * @subpackage filters + * @since 0.3 + */ +class LoggerFilterStringMatch extends LoggerFilter { + + /** + * @var boolean + */ + protected $acceptOnMatch = true; + + /** + * @var string + */ + protected $stringToMatch; + + /** + * @param mixed $acceptOnMatch a boolean or a string ('true' or 'false') + */ + public function setAcceptOnMatch($acceptOnMatch) { + $this->setBoolean('acceptOnMatch', $acceptOnMatch); + } + + /** + * @param string $s the string to match + */ + public function setStringToMatch($string) { + $this->setString('stringToMatch', $string); + } + + /** + * @return integer a {@link LOGGER_FILTER_NEUTRAL} is there is no string match. + */ + public function decide(LoggerLoggingEvent $event) { + $msg = $event->getRenderedMessage(); + + if($msg === null or $this->stringToMatch === null) { + return LoggerFilter::NEUTRAL; + } + + if(strpos($msg, $this->stringToMatch) !== false ) { + return ($this->acceptOnMatch) ? LoggerFilter::ACCEPT : LoggerFilter::DENY; + } + return LoggerFilter::NEUTRAL; + } +} diff --git a/sources/lib/Log4php/helpers/LoggerFormattingInfo.php b/sources/lib/Log4php/helpers/LoggerFormattingInfo.php new file mode 100644 index 0000000..a8a54a6 --- /dev/null +++ b/sources/lib/Log4php/helpers/LoggerFormattingInfo.php @@ -0,0 +1,54 @@ +$key using this search criteria: + * - if $key is a constant then return it. Else + * - if $key is set in $_ENV then return it. Else + * - return $def. + * + * @param string $key The key to search for. + * @param string $def The default value to return. + * @return string the string value of the system property, or the default + * value if there is no property with that key. + */ + public static function getSystemProperty($key, $def) { + if(defined($key)) { + return (string)constant($key); + } else if(isset($_SERVER[$key])) { + return (string)$_SERVER[$key]; + } else if(isset($_ENV[$key])) { + return (string)$_ENV[$key]; + } else { + return $def; + } + } + + /** Converts $value to boolean, or throws an exception if not possible. */ + public static function toBooleanEx($value) { + if (isset($value)) { + if (is_bool($value)) { + return $value; + } + $value = strtolower(trim($value)); + if (in_array($value, self::$trueValues)) { + return true; + } + if (in_array($value, self::$falseValues)) { + return false; + } + } + + throw new LoggerException("Given value [" . var_export($value, true) . "] cannot be converted to boolean."); + } + + /** + * Converts $value to integer, or throws an exception if not possible. + * Floats cannot be converted to integer. + */ + public static function toIntegerEx($value) { + if (is_integer($value)) { + return $value; + } + if (is_numeric($value) && ($value == (integer) $value)) { + return (integer) $value; + } + + throw new LoggerException("Given value [" . var_export($value, true) . "] cannot be converted to integer."); + } + + /** + * Converts $value to integer, or throws an exception if not possible. + * Floats cannot be converted to integer. + */ + public static function toPositiveIntegerEx($value) { + if (is_integer($value) && $value > 0) { + return $value; + } + if (is_numeric($value) && ($value == (integer) $value) && $value > 0) { + return (integer) $value; + } + + throw new LoggerException("Given value [" . var_export($value, true) . "] cannot be converted to a positive integer."); + } + + /** Converts the value to a level. Throws an exception if not possible. */ + public static function toLevelEx($value) { + if ($value instanceof LoggerLevel) { + return $value; + } + $level = LoggerLevel::toLevel($value); + if ($level === null) { + throw new LoggerException("Given value [" . var_export($value, true) . "] cannot be converted to a logger level."); + } + return $level; + } + + /** + * Converts a value to a valid file size (integer). + * + * Supports 'KB', 'MB' and 'GB' suffixes, where KB = 1024 B etc. + * + * The final value will be rounded to the nearest integer. + * + * Examples: + * - '100' => 100 + * - '100.12' => 100 + * - '100KB' => 102400 + * - '1.5MB' => 1572864 + * + * @param mixed $value File size (optionally with suffix). + * @return integer Parsed file size. + */ + public static function toFileSizeEx($value) { + + if (empty($value)) { + throw new LoggerException("Empty value cannot be converted to a file size."); + } + + if (is_numeric($value)) { + return (integer) $value; + } + + if (!is_string($value)) { + throw new LoggerException("Given value [" . var_export($value, true) . "] cannot be converted to a file size."); + } + + $str = strtoupper(trim($value)); + $count = preg_match('/^([0-9.]+)(KB|MB|GB)?$/', $str, $matches); + + if ($count > 0) { + $size = $matches[1]; + $unit = $matches[2]; + + switch($unit) { + case 'KB': $size *= pow(1024, 1); break; + case 'MB': $size *= pow(1024, 2); break; + case 'GB': $size *= pow(1024, 3); break; + } + + return (integer) $size; + } + + throw new LoggerException("Given value [$value] cannot be converted to a file size."); + } + + /** + * Converts a value to string, or throws an exception if not possible. + * + * Objects can be converted to string if they implement the magic + * __toString() method. + * + */ + public static function toStringEx($value) { + if (is_string($value)) { + return $value; + } + if (is_numeric($value)) { + return (string) $value; + } + if (is_object($value) && method_exists($value, '__toString')) { + return (string) $value; + } + + throw new LoggerException("Given value [" . var_export($value, true) . "] cannot be converted to string."); + } + + /** + * Performs value substitution for string options. + * + * An option can contain PHP constants delimited by '${' and '}'. + * + * E.g. for input string "some ${FOO} value", the method will attempt + * to substitute ${FOO} with the value of constant FOO if it exists. + * + * Therefore, if FOO is a constant, and it has value "bar", the resulting + * string will be "some bar value". + * + * If the constant is not defined, it will be replaced by an empty string, + * and the resulting string will be "some value". + * + * @param string $string String on which to perform substitution. + * @return string + */ + public static function substConstants($string) { + preg_match_all('/\${([^}]+)}/', $string, $matches); + + foreach($matches[1] as $key => $match) { + $match = trim($match); + $search = $matches[0][$key]; + $replacement = defined($match) ? constant($match) : ''; + $string = str_replace($search, $replacement, $string); + } + return $string; + } +} diff --git a/sources/lib/Log4php/helpers/LoggerPatternParser.php b/sources/lib/Log4php/helpers/LoggerPatternParser.php new file mode 100644 index 0000000..2b41e0a --- /dev/null +++ b/sources/lib/Log4php/helpers/LoggerPatternParser.php @@ -0,0 +1,237 @@ +It is this class that parses conversion patterns and creates + * a chained list of {@link LoggerPatternConverter} converters.

    + * + * @version $Revision: 1395467 $ + * @package log4php + * @subpackage helpers + * + * @since 0.3 + */ +class LoggerPatternParser { + + /** Escape character for conversion words in the conversion pattern. */ + const ESCAPE_CHAR = '%'; + + /** Maps conversion words to relevant converters. */ + private $converterMap; + + /** Conversion pattern used in layout. */ + private $pattern; + + /** Regex pattern used for parsing the conversion pattern. */ + private $regex; + + /** + * First converter in the chain. + * @var LoggerPatternConverter + */ + private $head; + + /** Last converter in the chain. */ + private $tail; + + public function __construct($pattern, $converterMap) { + $this->pattern = $pattern; + $this->converterMap = $converterMap; + + // Construct the regex pattern + $this->regex = + '/' . // Starting regex pattern delimiter + self::ESCAPE_CHAR . // Character which marks the start of the conversion pattern + '(?P[0-9.-]*)' . // Format modifiers (optional) + '(?P[a-zA-Z]+)' . // The conversion word + '(?P