Repository: rathena/FluxCP
Branch: master
Commit: 98cd4b8fd832
Files: 559
Total size: 1.7 MB
Directory structure:
gitextract_z0fge0vx/
├── .deepsource.toml
├── .editorconfig
├── .github/
│ ├── ISSUE_TEMPLATE/
│ │ ├── bug_report.yml
│ │ ├── config.yml
│ │ └── enhancement_request.yml
│ ├── PULL_REQUEST_TEMPLATE/
│ │ └── pull_request_template.md
│ ├── pull_request_template.md
│ └── workflows/
│ └── codeql.yml
├── .gitignore
├── .htaccess
├── LICENSE
├── README.md
├── addons/
│ └── helloworld/
│ ├── config/
│ │ ├── access.php
│ │ └── addon.php
│ ├── lang/
│ │ ├── en_us.php
│ │ └── ko_kr.php
│ ├── modules/
│ │ └── helloworld/
│ │ └── index.php
│ └── themes/
│ └── default/
│ └── helloworld/
│ └── index.php
├── config/
│ ├── .htaccess
│ ├── access.php
│ ├── application.php
│ ├── attributes.php
│ ├── cashshopcategories.php
│ ├── castlenames.php
│ ├── elements.php
│ ├── equip_jobs.php
│ ├── equip_location_combinations.php
│ ├── equip_locations.php
│ ├── equip_upper.php
│ ├── error.php
│ ├── feedingtypes.php
│ ├── groups.php
│ ├── homunculus.php
│ ├── item_randoptions.php
│ ├── itemsflags.php
│ ├── itemsubtypes.php
│ ├── itemtypes.php
│ ├── jobs.php
│ ├── jobs_alchemist.php
│ ├── jobs_blacksmith.php
│ ├── jobs_gender_linked.php
│ ├── loginerrors.php
│ ├── monster_ai.php
│ ├── monstermode.php
│ ├── picktypes.php
│ ├── races.php
│ ├── servers.php
│ ├── shopcategories.php
│ ├── sizes.php
│ └── trade_restrictions.php
├── data/
│ ├── captcha/
│ │ ├── fonts/
│ │ │ └── index.html
│ │ └── index.html
│ ├── emblem/
│ │ └── index.html
│ ├── index.html
│ ├── items/
│ │ ├── icons/
│ │ │ └── index.html
│ │ ├── images/
│ │ │ └── index.html
│ │ └── index.html
│ ├── itemshop/
│ │ └── index.html
│ ├── jobs/
│ │ ├── images/
│ │ │ ├── F/
│ │ │ │ └── index.html
│ │ │ ├── M/
│ │ │ │ └── index.html
│ │ │ └── index.html
│ │ └── index.html
│ ├── logs/
│ │ └── index.html
│ ├── monsters/
│ │ └── index.html
│ ├── npc/
│ │ ├── DonationNPC.txt
│ │ ├── PeakNPC.txt
│ │ ├── index.html
│ │ ├── support_cmd.txt
│ │ └── web_commands.txt
│ ├── paypal/
│ │ ├── button.php
│ │ └── index.html
│ ├── schemas/
│ │ ├── charmapdb/
│ │ │ ├── cp_charprefs.20080929191525.sql
│ │ │ ├── cp_charprefs.20081109093448.sql
│ │ │ ├── cp_charprefs.20120816150540.sql
│ │ │ ├── cp_commands.20160608065501.sql
│ │ │ ├── cp_itemdesc.20170210033400.sql
│ │ │ ├── cp_itemshop.20080928225124.sql
│ │ │ ├── cp_itemshop.20081109093448.sql
│ │ │ ├── cp_itemshop.20081128093449.sql
│ │ │ ├── cp_itemshop.20090104190020.sql
│ │ │ ├── cp_onlinepeak.20131120120201.sql
│ │ │ ├── cp_redeemlog.20080928225124.sql
│ │ │ ├── cp_redeemlog.20081001054354.sql
│ │ │ ├── cp_redeemlog.20081109093448.sql
│ │ │ ├── cp_xferlog.20080928225124.sql
│ │ │ ├── cp_xferlog.20081109093448.sql
│ │ │ └── index.html
│ │ ├── index.html
│ │ └── logindb/
│ │ ├── cp_banlog.20131213174201.sql
│ │ ├── cp_cmsnews.20131120145701.sql
│ │ ├── cp_cmspages.20131120161901.sql
│ │ ├── cp_cmssettings.20131120145801.sql
│ │ ├── cp_createlog.20131213174201.sql
│ │ ├── cp_createlog.20170720151901.sql
│ │ ├── cp_createlog.20250614124331.sql
│ │ ├── cp_credits.20131213174201.sql
│ │ ├── cp_emailchange.20131213174201.sql
│ │ ├── cp_emailchange.20250609133400.sql
│ │ ├── cp_ipbanlog.20120816150540.sql
│ │ ├── cp_ipbanlog.20250609133400.sql
│ │ ├── cp_loginlog.20131213174201.sql
│ │ ├── cp_loginlog.20250609133400.sql
│ │ ├── cp_loginprefs.20131213174201.sql
│ │ ├── cp_pwchange.20131213174201.sql
│ │ ├── cp_pwchange.20250609133400.sql
│ │ ├── cp_resetpass.20131213174201.sql
│ │ ├── cp_resetpass.20250609133400.sql
│ │ ├── cp_servicedesk.20131122010001.sql
│ │ ├── cp_servicedesk.20250609133400.sql
│ │ ├── cp_servicedeska.20131122010001.sql
│ │ ├── cp_servicedeska.20250609133400.sql
│ │ ├── cp_servicedeskcat.20131122010001.sql
│ │ ├── cp_servicedesksettings.20131122010001.sql
│ │ ├── cp_trusted.20131213174201.sql
│ │ ├── cp_txnlog.20131213174201.sql
│ │ ├── cp_txnlog.20170217073601.sql
│ │ ├── cp_txnlog.20170221184601.sql
│ │ └── index.html
│ ├── templates/
│ │ ├── changemail.php
│ │ ├── confirm.php
│ │ ├── contactform.php
│ │ ├── index.html
│ │ ├── marketing1.php
│ │ ├── newpass.php
│ │ ├── newticket.php
│ │ ├── resetpass.php
│ │ └── ticketreply.php
│ └── tmp/
│ ├── index.html
│ └── transactions/
│ ├── Refunded/
│ │ └── index.html
│ ├── Reversed/
│ │ └── index.html
│ └── web_accept/
│ ├── Completed/
│ │ └── index.html
│ ├── Denied/
│ │ └── index.html
│ └── Pending/
│ └── index.html
├── doc/
│ ├── user_lang.md
│ └── user_theme.md
├── error.php
├── index.php
├── lang/
│ ├── en_us.php
│ ├── es_es.php
│ ├── id_id.php
│ └── pt_br.php
├── lib/
│ ├── Flux/
│ │ ├── Addon.php
│ │ ├── Athena.php
│ │ ├── Authorization.php
│ │ ├── BaseServer.php
│ │ ├── Captcha.php
│ │ ├── CharServer.php
│ │ ├── Config.php
│ │ ├── Connection/
│ │ │ ├── Statement.php
│ │ │ └── index.html
│ │ ├── Connection.php
│ │ ├── DataObject.php
│ │ ├── Dispatcher.php
│ │ ├── EmblemExporter.php
│ │ ├── Error.php
│ │ ├── FileLoad.php
│ │ ├── Installer/
│ │ │ ├── CharMapServer.php
│ │ │ ├── MainServer.php
│ │ │ ├── Schema.php
│ │ │ ├── SchemaPermissionError.php
│ │ │ └── index.html
│ │ ├── Installer.php
│ │ ├── ItemExistsError.php
│ │ ├── ItemShop/
│ │ │ ├── Cart.php
│ │ │ └── index.html
│ │ ├── ItemShop.php
│ │ ├── LogFile.php
│ │ ├── LoginAthenaGroup.php
│ │ ├── LoginError.php
│ │ ├── LoginServer.php
│ │ ├── Mailer.php
│ │ ├── MapServer.php
│ │ ├── Paginator.php
│ │ ├── PaymentNotifyRequest.php
│ │ ├── PermissionError.php
│ │ ├── RegisterError.php
│ │ ├── SessionData.php
│ │ ├── Template.php
│ │ ├── TemporaryTable.php
│ │ └── index.html
│ ├── Flux.php
│ ├── functions/
│ │ ├── discordwebhook.php
│ │ ├── getReposVersion.php
│ │ ├── imagecreatefrombmpstring.php
│ │ └── index.html
│ ├── index.html
│ └── phpmailer/
│ ├── LICENSE
│ ├── PHPMailerAutoload.php
│ ├── VERSION
│ ├── class.phpmailer.php
│ ├── class.pop3.php
│ ├── class.smtp.php
│ ├── extras/
│ │ ├── EasyPeasyICS.php
│ │ ├── README.md
│ │ ├── htmlfilter.php
│ │ └── ntlm_sasl_client.php
│ ├── index.html
│ └── language/
│ ├── index.html
│ ├── phpmailer.lang-am.php
│ ├── phpmailer.lang-ar.php
│ ├── phpmailer.lang-az.php
│ ├── phpmailer.lang-be.php
│ ├── phpmailer.lang-bg.php
│ ├── phpmailer.lang-br.php
│ ├── phpmailer.lang-ca.php
│ ├── phpmailer.lang-ch.php
│ ├── phpmailer.lang-cs.php
│ ├── phpmailer.lang-cz.php
│ ├── phpmailer.lang-da.php
│ ├── phpmailer.lang-de.php
│ ├── phpmailer.lang-dk.php
│ ├── phpmailer.lang-el.php
│ ├── phpmailer.lang-eo.php
│ ├── phpmailer.lang-es.php
│ ├── phpmailer.lang-et.php
│ ├── phpmailer.lang-fa.php
│ ├── phpmailer.lang-fi.php
│ ├── phpmailer.lang-fo.php
│ ├── phpmailer.lang-fr.php
│ ├── phpmailer.lang-gl.php
│ ├── phpmailer.lang-he.php
│ ├── phpmailer.lang-hr.php
│ ├── phpmailer.lang-hu.php
│ ├── phpmailer.lang-id.php
│ ├── phpmailer.lang-it.php
│ ├── phpmailer.lang-ja.php
│ ├── phpmailer.lang-ka.php
│ ├── phpmailer.lang-ko.php
│ ├── phpmailer.lang-lt.php
│ ├── phpmailer.lang-lv.php
│ ├── phpmailer.lang-ms.php
│ ├── phpmailer.lang-nb.php
│ ├── phpmailer.lang-nl.php
│ ├── phpmailer.lang-no.php
│ ├── phpmailer.lang-pl.php
│ ├── phpmailer.lang-pt.php
│ ├── phpmailer.lang-pt_br.php
│ ├── phpmailer.lang-ro.php
│ ├── phpmailer.lang-ru.php
│ ├── phpmailer.lang-se.php
│ ├── phpmailer.lang-sk.php
│ ├── phpmailer.lang-sl.php
│ ├── phpmailer.lang-sr.php
│ ├── phpmailer.lang-sv.php
│ ├── phpmailer.lang-tr.php
│ ├── phpmailer.lang-uk.php
│ ├── phpmailer.lang-vi.php
│ ├── phpmailer.lang-zh.php
│ └── phpmailer.lang-zh_cn.php
├── modules/
│ ├── .htaccess
│ ├── account/
│ │ ├── cart.php
│ │ ├── changemail.php
│ │ ├── changepass.php
│ │ ├── changesex.php
│ │ ├── confirm.php
│ │ ├── confirmemail.php
│ │ ├── create.php
│ │ ├── edit.php
│ │ ├── index.php
│ │ ├── login.php
│ │ ├── logout.php
│ │ ├── pagemenu/
│ │ │ └── view.php
│ │ ├── prune.php
│ │ ├── resend.php
│ │ ├── resetpass.php
│ │ ├── resetpw.php
│ │ ├── transfer.php
│ │ ├── view.php
│ │ └── xferlog.php
│ ├── auction/
│ │ └── index.php
│ ├── buyingstore/
│ │ ├── index.php
│ │ └── viewshop.php
│ ├── captcha/
│ │ └── index.php
│ ├── castle/
│ │ └── index.php
│ ├── character/
│ │ ├── changeslot.php
│ │ ├── divorce.php
│ │ ├── index.php
│ │ ├── mapstats.php
│ │ ├── online.php
│ │ ├── pagemenu/
│ │ │ └── view.php
│ │ ├── prefs.php
│ │ ├── resetlook.php
│ │ ├── resetpos.php
│ │ └── view.php
│ ├── cplog/
│ │ ├── ban.php
│ │ ├── changemail.php
│ │ ├── changepass.php
│ │ ├── create.php
│ │ ├── index.php
│ │ ├── ipban.php
│ │ ├── login.php
│ │ ├── paypal.php
│ │ ├── resetpass.php
│ │ └── txnview.php
│ ├── donate/
│ │ ├── complete.php
│ │ ├── history.php
│ │ ├── index.php
│ │ ├── notify.php
│ │ ├── trusted.php
│ │ └── update.php
│ ├── economy/
│ │ └── index.php
│ ├── errors/
│ │ ├── missing_action.php
│ │ └── missing_view.php
│ ├── forum/
│ │ └── index.php
│ ├── guild/
│ │ ├── emblem.php
│ │ ├── export.php
│ │ ├── index.php
│ │ └── view.php
│ ├── history/
│ │ ├── cplogin.php
│ │ ├── emailchange.php
│ │ ├── gamelogin.php
│ │ ├── index.php
│ │ ├── passchange.php
│ │ └── passreset.php
│ ├── install/
│ │ ├── index.php
│ │ └── reinstall.php
│ ├── ipban/
│ │ ├── add.php
│ │ ├── edit.php
│ │ ├── index.php
│ │ ├── remove.php
│ │ └── unban.php
│ ├── item/
│ │ ├── index.php
│ │ ├── iteminfo.php
│ │ ├── pagemenu/
│ │ │ └── view.php
│ │ └── view.php
│ ├── itemshop/
│ │ ├── add.php
│ │ ├── delete.php
│ │ ├── edit.php
│ │ └── imagedel.php
│ ├── logdata/
│ │ ├── branch.php
│ │ ├── cashpoints.php
│ │ ├── char.php
│ │ ├── chat.php
│ │ ├── command.php
│ │ ├── feeding.php
│ │ ├── index.php
│ │ ├── inter.php
│ │ ├── login.php
│ │ ├── mvp.php
│ │ ├── npc.php
│ │ ├── pick.php
│ │ └── zeny.php
│ ├── mail/
│ │ └── index.php
│ ├── main/
│ │ ├── index.php
│ │ ├── page_not_found.php
│ │ └── preprocess.php
│ ├── monster/
│ │ ├── index.php
│ │ └── view.php
│ ├── news/
│ │ ├── add.php
│ │ ├── delete.php
│ │ ├── edit.php
│ │ ├── index.php
│ │ ├── manage.php
│ │ └── view.php
│ ├── pages/
│ │ ├── add.php
│ │ ├── content.php
│ │ ├── delete.php
│ │ ├── edit.php
│ │ └── index.php
│ ├── purchase/
│ │ ├── add.php
│ │ ├── cart.php
│ │ ├── checkout.php
│ │ ├── clear.php
│ │ ├── index.php
│ │ ├── pending.php
│ │ └── remove.php
│ ├── ranking/
│ │ ├── alchemist.php
│ │ ├── blacksmith.php
│ │ ├── character.php
│ │ ├── death.php
│ │ ├── guild.php
│ │ ├── homunculus.php
│ │ ├── mvp.php
│ │ └── zeny.php
│ ├── server/
│ │ ├── info.php
│ │ ├── status-xml.php
│ │ └── status.php
│ ├── service/
│ │ └── tos.php
│ ├── servicedesk/
│ │ ├── catcontrol.php
│ │ ├── create.php
│ │ ├── index.php
│ │ ├── staffindex.php
│ │ ├── staffsettings.php
│ │ ├── staffview.php
│ │ ├── staffviewclosed.php
│ │ └── view.php
│ ├── unauthorized/
│ │ └── index.php
│ ├── vending/
│ │ ├── index.php
│ │ └── viewshop.php
│ ├── webcommands/
│ │ └── index.php
│ └── woe/
│ ├── custom.php
│ └── index.php
├── robots.txt
└── themes/
├── bootstrap/
│ ├── css/
│ │ ├── flux/
│ │ │ ├── ie.css
│ │ │ └── unitip.css
│ │ ├── flux.css
│ │ └── sticky-footer-navbar.css
│ ├── footer.php
│ ├── header.php
│ ├── js/
│ │ └── ie9.js
│ ├── main/
│ │ └── navbar.php
│ └── manifest.php
├── default/
│ ├── account/
│ │ ├── cart.php
│ │ ├── changemail.php
│ │ ├── changepass.php
│ │ ├── changesex.php
│ │ ├── confirm.php
│ │ ├── confirmemail.php
│ │ ├── create.php
│ │ ├── edit.php
│ │ ├── index.php
│ │ ├── login.php
│ │ ├── logout.php
│ │ ├── prune.php
│ │ ├── resend.php
│ │ ├── resetpass.php
│ │ ├── resetpw.php
│ │ ├── transfer.php
│ │ ├── view.php
│ │ └── xferlog.php
│ ├── auction/
│ │ └── index.php
│ ├── buyingstore/
│ │ ├── index.php
│ │ └── viewshop.php
│ ├── captcha/
│ │ └── index.php
│ ├── castle/
│ │ └── index.php
│ ├── character/
│ │ ├── changeslot.php
│ │ ├── divorce.php
│ │ ├── index.php
│ │ ├── mapstats.php
│ │ ├── online.php
│ │ ├── prefs.php
│ │ ├── resetlook.php
│ │ ├── resetpos.php
│ │ └── view.php
│ ├── cplog/
│ │ ├── ban.php
│ │ ├── changemail.php
│ │ ├── changepass.php
│ │ ├── create.php
│ │ ├── index.php
│ │ ├── ipban.php
│ │ ├── login.php
│ │ ├── paypal.php
│ │ ├── resetpass.php
│ │ └── txnview.php
│ ├── css/
│ │ ├── flux/
│ │ │ ├── ie.css
│ │ │ ├── recaptcha.css
│ │ │ └── unitip.css
│ │ └── flux.css
│ ├── data/
│ │ └── FluxDefaultTheme.psd
│ ├── donate/
│ │ ├── complete.php
│ │ ├── history.php
│ │ ├── index.php
│ │ ├── notify.php
│ │ ├── trusted.php
│ │ └── update.php
│ ├── economy/
│ │ └── index.php
│ ├── errors/
│ │ ├── missing_action.php
│ │ └── missing_view.php
│ ├── footer.php
│ ├── forum/
│ │ └── index.php
│ ├── guild/
│ │ ├── emblem.php
│ │ ├── export.php
│ │ ├── index.php
│ │ └── view.php
│ ├── header.php
│ ├── history/
│ │ ├── cplogin.php
│ │ ├── emailchange.php
│ │ ├── gamelogin.php
│ │ ├── index.php
│ │ ├── passchange.php
│ │ └── passreset.php
│ ├── install/
│ │ └── reinstall.php
│ ├── ipban/
│ │ ├── add.php
│ │ ├── edit.php
│ │ ├── index.php
│ │ ├── remove.php
│ │ └── unban.php
│ ├── item/
│ │ ├── index.php
│ │ ├── iteminfo.php
│ │ └── view.php
│ ├── itemshop/
│ │ ├── add.php
│ │ ├── delete.php
│ │ ├── edit.php
│ │ └── imagedel.php
│ ├── js/
│ │ ├── flux.datefields.js
│ │ ├── flux.unitip.js
│ │ ├── flux.unitpngfix.js
│ │ ├── ie9.js
│ │ └── jquery-1.8.3.min.js
│ ├── logdata/
│ │ ├── branch.php
│ │ ├── cashpoints.php
│ │ ├── char.php
│ │ ├── chat.php
│ │ ├── command.php
│ │ ├── feeding.php
│ │ ├── index.php
│ │ ├── inter.php
│ │ ├── login.php
│ │ ├── mvp.php
│ │ ├── npc.php
│ │ ├── pick.php
│ │ └── zeny.php
│ ├── mail/
│ │ └── index.php
│ ├── main/
│ │ ├── balance.php
│ │ ├── index.php
│ │ ├── loginbox.php
│ │ ├── page_not_found.php
│ │ ├── pagemenu.php
│ │ ├── sidebar.php
│ │ └── submenu.php
│ ├── monster/
│ │ ├── index.php
│ │ └── view.php
│ ├── news/
│ │ ├── add.php
│ │ ├── delete.php
│ │ ├── edit.php
│ │ ├── index.php
│ │ ├── manage.php
│ │ └── view.php
│ ├── pages/
│ │ ├── add.php
│ │ ├── content.php
│ │ ├── delete.php
│ │ ├── edit.php
│ │ └── index.php
│ ├── purchase/
│ │ ├── add.php
│ │ ├── cart.php
│ │ ├── checkout.php
│ │ ├── clear.php
│ │ ├── index.php
│ │ ├── pending.php
│ │ └── remove.php
│ ├── ranking/
│ │ ├── alchemist.php
│ │ ├── blacksmith.php
│ │ ├── character.php
│ │ ├── death.php
│ │ ├── guild.php
│ │ ├── homunculus.php
│ │ ├── mvp.php
│ │ └── zeny.php
│ ├── server/
│ │ ├── info.php
│ │ ├── status-xml.php
│ │ └── status.php
│ ├── service/
│ │ └── tos.php
│ ├── servicedesk/
│ │ ├── catcontrol.php
│ │ ├── create.php
│ │ ├── index.php
│ │ ├── staffindex.php
│ │ ├── staffsettings.php
│ │ ├── staffview.php
│ │ ├── staffviewclosed.php
│ │ └── view.php
│ ├── unauthorized/
│ │ └── index.php
│ ├── vending/
│ │ ├── index.php
│ │ └── viewshop.php
│ ├── webcommands/
│ │ └── index.php
│ └── woe/
│ ├── custom.php
│ └── index.php
└── installer/
├── footer.php
├── header.php
└── install/
└── index.php
================================================
FILE CONTENTS
================================================
================================================
FILE: .deepsource.toml
================================================
version = 1
[[analyzers]]
name = "php"
[[analyzers]]
name = "javascript"
[[analyzers]]
name = "sql"
exclude_files = [
"lib/phpmailer/**",
]
================================================
FILE: .editorconfig
================================================
# rAthena FluxCP EditorConfig file
# EditorConfig is awesome: https://EditorConfig.org
root = true
[*]
insert_final_newline = true
trim_trailing_whitespace = true
[*.php]
charset = utf-8
end_of_line = lf
indent_style = space
indent_size = 4
[*.json]
indent_style = space
indent_size = 2
[*.yml]
indent_style = space
indent_size = 2
================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.yml
================================================
name: Bug Report
description: Create an Issue that details broken or unexpected behaviour
labels: [Bug Report]
body:
- type: textarea
attributes:
label: Environment
description: |
examples:
- **PHP**: 8.1
- **MySQL**: MariaDB 10
value: |
- PHP:
- MySQL:
render: Markdown
validations:
required: true
- type: textarea
attributes:
label: Current Behavior
description: A concise description of what you're experiencing.
validations:
required: true
- type: textarea
attributes:
label: Expected Behavior
description: A concise description of what you expected to happen.
validations:
required: false
- type: textarea
attributes:
label: Steps To Reproduce
description: Steps to reproduce the behavior.
placeholder: |
1. In this environment...
2. With this config...
3. Open page '...'
4. See error...
validations:
required: true
- type: textarea
attributes:
label: Anything else?
description: |
Links? References? Anything that will give us more context about the issue you are encountering!
Tip: You can attach images or log files by clicking this area to highlight it and then dragging files in.
validations:
required: false
================================================
FILE: .github/ISSUE_TEMPLATE/config.yml
================================================
blank_issues_enabled: false
contact_links:
- name: Forum Support
url: https://rathena.org/board/forum/21-web-support/
about: You can obtain support for FluxCP on our forum.
- name: Discord Support
url: https://discord.gg/kMeMXWEvSV
about: Chat with others in our fluxcp-support channel.
================================================
FILE: .github/ISSUE_TEMPLATE/enhancement_request.yml
================================================
name: Enhancement Request
description: Submit your request for changes to existing code to provide more functionality or QoL changes.
title: "Enhancement Request"
labels: [Enhancement Request]
body:
- type: textarea
attributes:
label: Provide Details
description: Give as much information as possible in order to get the discussion going.
validations:
required: true
================================================
FILE: .github/PULL_REQUEST_TEMPLATE/pull_request_template.md
================================================
Fixes # .
Changes proposed in this Pull Request:
* ?
* ?
* ?
================================================
FILE: .github/pull_request_template.md
================================================
Fixes # .
Changes proposed in this Pull Request:
* ?
* ?
* ?
================================================
FILE: .github/workflows/codeql.yml
================================================
name: "CodeQL"
on:
push:
branches: [ "master" ]
pull_request:
branches: [ "master" ]
schedule:
- cron: "36 3 * * 3"
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language: [ javascript ]
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
with:
languages: ${{ matrix.language }}
queries: +security-and-quality
- name: Autobuild
uses: github/codeql-action/autobuild@v2
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
with:
category: "/language:${{ matrix.language }}"
================================================
FILE: .gitignore
================================================
# Images
/data/items/icons/*.png
/data/items/images/*.png
/data/jobs/images/F/*.gif
/data/jobs/images/M/*.gif
/data/monsters/*.png
/data/monsters/*.gif
# Logs
/data/logs/errors
/data/logs/mail
/data/logs/mysql
/data/logs/schemas
/data/logs/transactions
# Caches
/data/tmp/*.php
/data/tmp/*.cache
/data/tmp/emblems
# Non-Default Themes
/themes/*
!/themes/default
!/themes/bootstrap
!/themes/installer
# Import Configs
/config/import
================================================
FILE: .htaccess
================================================
#RewriteEngine On
#RewriteBase /
#RewriteCond %{REQUEST_FILENAME} !-f
#RewriteCond %{REQUEST_FILENAME} !-d
#RewriteRule !\.(js|ico|gif|jpg|png|css|pdf|swf|flv)$ index.php [QSA]
================================================
FILE: LICENSE
================================================
GNU LESSER 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.
This version of the GNU Lesser General Public License incorporates
the terms and conditions of version 3 of the GNU General Public
License, supplemented by the additional permissions listed below.
0. Additional Definitions.
As used herein, "this License" refers to version 3 of the GNU Lesser
General Public License, and the "GNU GPL" refers to version 3 of the GNU
General Public License.
"The Library" refers to a covered work governed by this License,
other than an Application or a Combined Work as defined below.
An "Application" is any work that makes use of an interface provided
by the Library, but which is not otherwise based on the Library.
Defining a subclass of a class defined by the Library is deemed a mode
of using an interface provided by the Library.
A "Combined Work" is a work produced by combining or linking an
Application with the Library. The particular version of the Library
with which the Combined Work was made is also called the "Linked
Version".
The "Minimal Corresponding Source" for a Combined Work means the
Corresponding Source for the Combined Work, excluding any source code
for portions of the Combined Work that, considered in isolation, are
based on the Application, and not on the Linked Version.
The "Corresponding Application Code" for a Combined Work means the
object code and/or source code for the Application, including any data
and utility programs needed for reproducing the Combined Work from the
Application, but excluding the System Libraries of the Combined Work.
1. Exception to Section 3 of the GNU GPL.
You may convey a covered work under sections 3 and 4 of this License
without being bound by section 3 of the GNU GPL.
2. Conveying Modified Versions.
If you modify a copy of the Library, and, in your modifications, a
facility refers to a function or data to be supplied by an Application
that uses the facility (other than as an argument passed when the
facility is invoked), then you may convey a copy of the modified
version:
a) under this License, provided that you make a good faith effort to
ensure that, in the event an Application does not supply the
function or data, the facility still operates, and performs
whatever part of its purpose remains meaningful, or
b) under the GNU GPL, with none of the additional permissions of
this License applicable to that copy.
3. Object Code Incorporating Material from Library Header Files.
The object code form of an Application may incorporate material from
a header file that is part of the Library. You may convey such object
code under terms of your choice, provided that, if the incorporated
material is not limited to numerical parameters, data structure
layouts and accessors, or small macros, inline functions and templates
(ten or fewer lines in length), you do both of the following:
a) Give prominent notice with each copy of the object code that the
Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the object code with a copy of the GNU GPL and this license
document.
4. Combined Works.
You may convey a Combined Work under terms of your choice that,
taken together, effectively do not restrict modification of the
portions of the Library contained in the Combined Work and reverse
engineering for debugging such modifications, if you also do each of
the following:
a) Give prominent notice with each copy of the Combined Work that
the Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the Combined Work with a copy of the GNU GPL and this license
document.
c) For a Combined Work that displays copyright notices during
execution, include the copyright notice for the Library among
these notices, as well as a reference directing the user to the
copies of the GNU GPL and this license document.
d) Do one of the following:
0) Convey the Minimal Corresponding Source under the terms of this
License, and the Corresponding Application Code in a form
suitable for, and under terms that permit, the user to
recombine or relink the Application with a modified version of
the Linked Version to produce a modified Combined Work, in the
manner specified by section 6 of the GNU GPL for conveying
Corresponding Source.
1) Use a suitable shared library mechanism for linking with the
Library. A suitable mechanism is one that (a) uses at run time
a copy of the Library already present on the user's computer
system, and (b) will operate properly with a modified version
of the Library that is interface-compatible with the Linked
Version.
e) Provide Installation Information, but only if you would otherwise
be required to provide such information under section 6 of the
GNU GPL, and only to the extent that such information is
necessary to install and execute a modified version of the
Combined Work produced by recombining or relinking the
Application with a modified version of the Linked Version. (If
you use option 4d0, the Installation Information must accompany
the Minimal Corresponding Source and Corresponding Application
Code. If you use option 4d1, you must provide the Installation
Information in the manner specified by section 6 of the GNU GPL
for conveying Corresponding Source.)
5. Combined Libraries.
You may place library facilities that are a work based on the
Library side by side in a single library together with other library
facilities that are not Applications and are not covered by this
License, and convey such a combined library under terms of your
choice, if you do both of the following:
a) Accompany the combined library with a copy of the same work based
on the Library, uncombined with any other library facilities,
conveyed under the terms of this License.
b) Give prominent notice with the combined library that part of it
is a work based on the Library, and explaining where to find the
accompanying uncombined form of the same work.
6. Revised Versions of the GNU Lesser General Public License.
The Free Software Foundation may publish revised and/or new versions
of the GNU Lesser 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
Library as you received it specifies that a certain numbered version
of the GNU Lesser General Public License "or any later version"
applies to it, you have the option of following the terms and
conditions either of that published version or of any later version
published by the Free Software Foundation. If the Library as you
received it does not specify a version number of the GNU Lesser
General Public License, you may choose any version of the GNU Lesser
General Public License ever published by the Free Software Foundation.
If the Library as you received it specifies that a proxy can decide
whether future versions of the GNU Lesser General Public License shall
apply, that proxy's public statement of acceptance of any version is
permanent authorization for you to choose that version for the
Library.
================================================
FILE: README.md
================================================
FluxCP
======
[](https://app.deepsource.com/gh/rathena/FluxCP/?ref=repository-badge)
[](https://lgtm.com/projects/g/rathena/FluxCP/alerts/)
[](https://lgtm.com/projects/g/rathena/FluxCP/alerts/)
[](https://www.codacy.com/gh/rathena/FluxCP/dashboard?utm_source=github.com&utm_medium=referral&utm_content=rathena/FluxCP&utm_campaign=Badge_Grade)
Flux Control Panel (FluxCP) for rAthena servers.
Requirements
---------
* PHP 7.3 or newer
* PDO and PDO-MYSQL extensions for PHP (including PHP_MYSQL support)
* MySQL 5 or newer
* Optional: GD2 (for guild emblems and registration CAPTCHA)
* Optional: Tidy (for cleaner HTML output)
* Optional: mod_rewrite support for UseCleanUrls feature
* Optional: [Item images](http://rathena.org/board/files/file/2509-item-images/)
What's New?
---------
* The description files are kept upto-date with each new commit from rAthena.
* Pre-integrated themes:
- default
- Bootstrap
* Built-In:
- News and Pages CMS with integrated TinyMCE
- Service Desk (ITIL Incident Management style support ticket system)
- Additional Logs
- More Ranking Lists
- Discord Integration
- Google ReCaptcha
- Remote AtCommand functionality
How To ... ?
---------
We have a small doc library that covers:
* Basic User Documentation
- Installation
- [Themes](https://github.com/rathena/FluxCP/blob/master/doc/user_theme.md)
- [Languages](https://github.com/rathena/FluxCP/blob/master/doc/user_lang.md)
- Installing Addons
- Updating FluxCP
* Developer Documentation
- Creating an Addon
- Providing Addon updates
- Creating a custom Theme
Join the Discussion
---------
We have a discord server separate from rAthena just for FluxCP stuff!
The channels there can be used to obtain help, discuss testing, view anonymous feedback log, Github commits, etc.
https://discord.gg/kMeMXWEvSV
Extra Credits
---------
Original FluxCP created by Paradox924X and Byteflux with additional contributions from Xantara.
Some works from other users have been integrated into this project.
================================================
FILE: addons/helloworld/config/access.php
================================================
array(
'helloworld' => array(
'index' => AccountLevel::ADMIN
)
),
'features' => array(
// None.
)
)
?>
================================================
FILE: addons/helloworld/config/addon.php
================================================
array(
//'Hello World' => array('module' => 'helloworld')
)
)
?>
================================================
FILE: addons/helloworld/lang/en_us.php
================================================
'Hello, World!',
'HelloInfoText' => 'This is a “Hello World” sample from an add-on!',
'HelloVersionText' => 'Your Flux Control Panel version is %s'
);
?>
================================================
FILE: addons/helloworld/lang/ko_kr.php
================================================
'안녕하세요!',
'HelloInfoText' => '현제보고계시는건 샘플 애드온 프로그램입니다!',
'HelloVersionText' => '지금 사용하고계시는 플럭스 버젼은 %s입니다.'
);
?>
================================================
FILE: addons/helloworld/modules/helloworld/index.php
================================================
================================================
FILE: addons/helloworld/themes/default/helloworld/index.php
================================================
================================================
FILE: config/.htaccess
================================================
Order deny,allow
Deny from all
================================================
FILE: config/access.php
================================================
array(
'main' => array(
'*' => AccountLevel::ANYONE
),
'donate' => array(
'index' => AccountLevel::ANYONE,
'notify' => AccountLevel::ANYONE,
'update' => AccountLevel::ANYONE,
'complete' => AccountLevel::ANYONE,
'history' => AccountLevel::NORMAL,
'trusted' => AccountLevel::NORMAL
),
'purchase' => array(
'index' => AccountLevel::ANYONE,
'add' => AccountLevel::ANYONE,
'clear' => AccountLevel::NORMAL,
'cart' => AccountLevel::NORMAL,
'checkout' => AccountLevel::NORMAL,
'remove' => AccountLevel::NORMAL,
'pending' => AccountLevel::NORMAL
),
'itemshop' => array(
'add' => AccountLevel::ADMIN,
'edit' => AccountLevel::ADMIN,
'delete' => AccountLevel::ADMIN,
'imagedel' => AccountLevel::ADMIN
),
'account' => array(
'index' => AccountLevel::LOWGM,
'view' => AccountLevel::NORMAL,
'create' => AccountLevel::UNAUTH,
'login' => AccountLevel::UNAUTH,
'logout' => AccountLevel::NORMAL,
'transfer' => AccountLevel::NORMAL,
'xferlog' => AccountLevel::NORMAL,
'cart' => AccountLevel::NORMAL,
'changepass' => AccountLevel::NORMAL,
'edit' => AccountLevel::ADMIN,
'changesex' => AccountLevel::NORMAL,
'confirm' => AccountLevel::UNAUTH,
'resend' => AccountLevel::UNAUTH,
'resetpass' => AccountLevel::UNAUTH,
'resetpw' => AccountLevel::UNAUTH,
'changemail' => AccountLevel::NORMAL,
'confirmemail' => AccountLevel::NORMAL,
'prune' => AccountLevel::ANYONE
),
'character' => array(
'index' => AccountLevel::LOWGM,
'view' => AccountLevel::NORMAL,
'online' => AccountLevel::ANYONE,
'prefs' => AccountLevel::NORMAL,
'changeslot' => AccountLevel::NORMAL,
'resetlook' => AccountLevel::NORMAL,
'resetpos' => AccountLevel::NORMAL,
'mapstats' => AccountLevel::ANYONE,
'divorce' => AccountLevel::NORMAL
),
'guild' => array(
'emblem' => AccountLevel::ANYONE,
'index' => AccountLevel::LOWGM,
'export' => AccountLevel::ADMIN,
'view' => AccountLevel::NORMAL
),
'castle' => array(
'index' => AccountLevel::ANYONE
),
'economy' => array(
'index' => AccountLevel::NORMAL
),
'auction' => array(
'index' => AccountLevel::LOWGM
),
'ranking' => array(
'character' => AccountLevel::ANYONE,
'guild' => AccountLevel::ANYONE,
'zeny' => AccountLevel::ANYONE,
'death' => AccountLevel::ANYONE,
'homun' => AccountLevel::ANYONE,
'swordman' => AccountLevel::ANYONE,
'bowman' => AccountLevel::ANYONE,
'spearman' => AccountLevel::ANYONE,
'mvp' => AccountLevel::ANYONE,
),
'item' => array(
'index' => AccountLevel::ANYONE,
'view' => AccountLevel::ANYONE,
'iteminfo' => AccountLevel::ADMIN
),
'monster' => array(
'index' => AccountLevel::ANYONE,
'view' => AccountLevel::ANYONE
),
'server' => array(
'status' => AccountLevel::ANYONE,
'status-xml' => AccountLevel::ANYONE,
'info' => AccountLevel::ANYONE
),
'logdata' => array(
'index' => AccountLevel::ADMIN,
'char' => AccountLevel::ADMIN,
'cashpoints' => AccountLevel::ADMIN,
'feeding' => AccountLevel::ADMIN,
'inter' => AccountLevel::ADMIN,
'command' => AccountLevel::ADMIN,
'branch' => AccountLevel::ADMIN,
'chat' => AccountLevel::ADMIN,
'login' => AccountLevel::ADMIN,
'mvp' => AccountLevel::ADMIN,
'npc' => AccountLevel::ADMIN,
'pick' => AccountLevel::ADMIN,
'zeny' => AccountLevel::ADMIN
),
'cplog' => array(
'index' => AccountLevel::ADMIN,
'create' => AccountLevel::ADMIN,
'paypal' => AccountLevel::ADMIN,
'login' => AccountLevel::ADMIN,
'resetpass' => AccountLevel::ADMIN,
'changepass' => AccountLevel::ADMIN,
'changemail' => AccountLevel::ADMIN,
'ban' => AccountLevel::ADMIN,
'ipban' => AccountLevel::ADMIN,
'txnview' => AccountLevel::ADMIN
),
'ipban' => array(
'index' => AccountLevel::ADMIN,
'add' => AccountLevel::ADMIN,
'unban' => AccountLevel::ADMIN,
'edit' => AccountLevel::ADMIN,
'remove' => AccountLevel::ADMIN
),
'service' => array(
'tos' => AccountLevel::ANYONE
),
'captcha' => array(
'index' => AccountLevel::ANYONE
),
'install' => array(
'index' => AccountLevel::ANYONE,
'reinstall' => AccountLevel::ADMIN
),
'test' => array(
'*' => AccountLevel::ANYONE
),
'woe' => array(
'index' => AccountLevel::ANYONE
),
'mail' => array(
'index' => AccountLevel::ADMIN
),
'history' => array(
'index' => AccountLevel::NORMAL,
'cplogin' => AccountLevel::NORMAL,
'gamelogin' => AccountLevel::NORMAL,
'emailchange' => AccountLevel::NORMAL,
'passchange' => AccountLevel::NORMAL,
'passreset' => AccountLevel::NORMAL
),
'pages' => array(
'index' => AccountLevel::ADMIN,
'add' => AccountLevel::ADMIN,
'delete' => AccountLevel::ADMIN,
'edit' => AccountLevel::ADMIN,
'content' => AccountLevel::ANYONE,
),
'news' => array(
'index' => AccountLevel::ANYONE,
'view' => AccountLevel::ANYONE,
'manage' => AccountLevel::ADMIN,
'add' => AccountLevel::ADMIN,
'edit' => AccountLevel::ADMIN,
'delete' => AccountLevel::ADMIN,
),
'servicedesk'=> array(
'index' => AccountLevel::NORMAL,
'create' => AccountLevel::NORMAL,
'view' => AccountLevel::NORMAL,
'staffindex' => AccountLevel::LOWGM,
'staffview' => AccountLevel::LOWGM,
'staffviewclosed'=> AccountLevel::LOWGM,
'staffsettings' => AccountLevel::LOWGM,
'catcontrol' => AccountLevel::HIGHGM
),
'vending' => array(
'index' => AccountLevel::ANYONE,
'viewshop' => AccountLevel::ANYONE,
),
'webcommands' => array(
'index' => AccountLevel::ADMIN,
),
),
// General feature permissions, handled by the modules themselves.
'features' => array(
'ViewAccount' => AccountLevel::HIGHGM, // View another person's account details.
'ViewAccountBanLog' => AccountLevel::HIGHGM, // View another person's account ban log.
'DeleteAccount' => AccountLevel::ADMIN, // (not yet implemented)
'DeleteCharacter' => AccountLevel::ADMIN, // (not yet implemented)
'SeeAccountPassword' => AccountLevel::NOONE, // If not using MD5, view another person's password in list.
'TempBanAccount' => AccountLevel::LOWGM, // Has ability to temporarily ban an account.
'TempUnbanAccount' => AccountLevel::LOWGM, // Has ability to remove a temporary ban on an account.
'PermBanAccount' => AccountLevel::HIGHGM, // Has ability to permanently ban an account.
'PermUnbanAccount' => AccountLevel::HIGHGM, // Has ability to remove a permanent ban on an account.
'SearchMD5Passwords' => AccountLevel::NOONE, // Ability to search MD5'd passwords in list.
'ViewCharacter' => AccountLevel::HIGHGM, // View another person's character details.
'DivorceCharacter' => AccountLevel::LOWGM, // Divorce another character.
'AddShopItem' => AccountLevel::ADMIN, // Ability to add an item to the shop.
'EditShopItem' => AccountLevel::ADMIN, // Ability to modify a shop item's details.
'DeleteShopItem' => AccountLevel::ADMIN, // Ability to remove an item for sale on the shop.
'ViewGuild' => AccountLevel::ADMIN, // Ability to view another guild's details.
'SearchWhosOnline' => AccountLevel::ANYONE, // Ability to search the "Who's Online" page.
'ViewOnlinePosition' => AccountLevel::LOWGM, // Ability to see a character's current map on "Who's Online" page.
'EditAccountGroupID' => AccountLevel::ADMIN, // Ability to edit another person's account group ID.
'EditAccountBalance' => AccountLevel::ADMIN, // Ability to edit another person's account balance.
'ModifyAccountPrefs' => AccountLevel::ADMIN, // Ability to modify another person's account preferences.
'ModifyCharPrefs' => AccountLevel::ADMIN, // Ability to modify another person's character preferences.
'IgnoreHiddenPref' => AccountLevel::LOWGM, // Ability to see users on "Who's Online" page, hidden or not.
'IgnoreHiddenPref2' => AccountLevel::LOWGM, // Ability to see users on "Who's Online" page, hidden by app config or not.
'SeeHiddenMapStats' => AccountLevel::LOWGM, // Ability to see hidden map statistics.
'ChangeSlot' => AccountLevel::LOWGM, // Minimum group level required to change another character's slot.
'ModifyIpBan' => AccountLevel::ADMIN, // Minimum group level required to modify an existing IP ban.
'RemoveIpBan' => AccountLevel::ADMIN, // Minimum group level required to remove an existing IP ban.
'HideFromZenyRank' => AccountLevel::NORMAL, // Ability to set "Hide from zeny ranking" pref.
'SeeItemDbScripts' => AccountLevel::ANYONE, // Ability to see item_db scripts in view page.
'SeeItemDb2Scripts' => AccountLevel::ADMIN, // Ability to see item_db2 scripts in view page.
'ViewRawTxnLogData' => AccountLevel::ADMIN, // Minimum group level required to view Raw Transaction Log in txnview page.
'ResetLook' => AccountLevel::LOWGM, // Minimum group level required to reset another character's look.
'ResetPosition' => AccountLevel::LOWGM, // Minimum group level required to reset another character's position.
'ViewWoeDisallowed' => AccountLevel::LOWGM, // Minimum group level required to bypass WoE-disabled page security check.
'SeeCpLoginLogPass' => AccountLevel::NOONE, // Minimum group level required to see password in CP login log (also requires CpLoginLogShowPassword in application.php)
'SearchCpLoginLogPw' => AccountLevel::NOONE, // Minimum group level required to search through passwords in the CP login log.
'SeeCpResetPass' => AccountLevel::NOONE, // Minimum group level required to see passwords in CP log's "password resets" page.
'SearchCpResetPass' => AccountLevel::NOONE, // Minimum group level required to search passwords in CP log's "password resets" page.
'SeeCpChangePass' => AccountLevel::NOONE, // Minimum group level required to see passwords in CP log's "password changes" page.
'SearchCpChangePass' => AccountLevel::NOONE, // Minimum group level required to search passwords in CP log's "password changes" page.
'SeeAccountID' => AccountLevel::LOWGM, // Minimum group level required to see Account ID on account view and character view pages.
'SeeUnknownItems' => AccountLevel::LOWGM, // Minimum group level required to see unidentified items as identified.
'AvoidSexChangeCost' => AccountLevel::LOWGM, // Avoid paying cost (if any) for sex changes.
'EditHigherPower' => AccountLevel::NOONE,
'BanHigherPower' => AccountLevel::NOONE
)
);
?>
================================================
FILE: config/application.php
================================================
'localhost', // This value is the hostname:port under which Flux runs. (e.g., example.com or example.com:80)
'BaseURI' => 'fluxcp', // The base URI is the base web root on which your application lies.
'ForceHTTPS' => true, // By default use HTTPS, you should only use HTTP, if you have no certificate available (Note: You may want to visit https://letsencrypt.org)
'InstallerPassword' => 'secretpassword', // Installer/updater password.
'RequireOwnership' => true, // Require the executing user to be owner of the FLUX_ROOT/data/ directory tree? (Better for security)
// WARNING: This will be mostly IGNORED on non-POSIX-compliant OSes (e.g. Windows).
'DefaultLoginGroup' => null,
'DefaultCharMapServer' => null,
'DefaultLanguage' => 'en_us', // Specify the default control panel language (see FLUX_ROOT/lang/ directory for available languages.)
'SiteTitle' => 'Flux Control Panel', // This value is only used if the theme decides to use it.
'ThemeName' => array('default', 'bootstrap'), // Names of the themes you would like list for use in the footer. Themes are in FLUX_ROOT/themes.
'ScriptTimeLimit' => 0, // Script execution time limit. Specifies (in seconds) how long a page should run before timing out. (0 means forever)
'MissingEmblemBMP' => 'empty.bmp', //
'ItemIconNameFormat' => '%d.png', // The filename format for item icons (defaults to {itemid}.png).
'ItemImageNameFormat' => '%d.png', // The filename format for item images (defaults to {itemid}.png).
'MonsterImageNameFormat' => '%d.gif', // The filename format for monster images (defaults to {monsterid}.gif).
'JobImageNameFormat' => '%d.gif', // The filename format for job images (defaults to {jobid}.gif).
'DivinePrideIntegration' => true, // Dowload monsters and items images from https://www.divine-pride.net if it's not exist.
'ForceEmptyEmblem' => false, // Forcefully display empty guild emblems, helpful when you don't have GD2 installed.
'EmblemCacheInterval' => 12, // Hourly interval to re-cache guild emblems (set to 0 to disable emblem cache).
'EmblemUseWebservice' => true, // Load emblems from WebService?
'SessionCookieExpire' => 48, // Duration in hours.
'AdminMenuGroupLevel' => AccountLevel::LOWGM, // The starting group ID for which module actions are moved into the admin menu for display.
'DateDefaultTimezone' => 'UTC', // The default timezone, consult the PHP manual for valid timezones: http://php.net/timezones (null for defaut system TZ)
'DateFormat' => 'Y-m-d', // Default DATE format to be displayed in pages.
'DateTimeFormat' => 'Y-m-d H:i:s', // Default DATETIME format to be displayed in pages.
'ShowSinglePage' => true, // Whether or not to show the page numbers even if there's only one page.
'ResultsPerPage' => 20, // The number of results to display in a paged set, per page.
'PagesToShow' => 10, // The number of page numbers to display at once.
'PageJumpMinimumPages' => 1, // Minimum number of required pages before page jump box is shown. (0 to always show!)
'ShowPageJump' => true, // Whether or not to show the "Page Jump" box.
'SingleMatchRedirect' => true, // Whether or not to redirect to view action from index page if only one match is returned (and action is allowed).
'SingleMatchRedirectItem' => false, // Same as above, for item module.
'SingleMatchRedirectMobs' => false, // Same as above, for monster module.
'UsernameAllowedChars' => 'a-zA-Z0-9_', // PCRE Format Pattern. default: 'a-zA-Z0-9_' (alphanumeric and underscore)
// WARNING: This string isn't escaped so be careful which chars you use!
// PCRE Pattern Ref: http://php.net/manual/en/pcre.pattern.php
'MinUsernameLength' => 4, // Minimum username length.
'MaxUsernameLength' => 23, // Maximum username length.
'MinPasswordLength' => 8, // Minimum password length.
'MaxPasswordLength' => 31, // Maximum password length.
'PasswordMinUpper' => 1, // Number of upper-case letters to require in passwords.
'PasswordMinLower' => 1, // Number of lower-case letters to require in passwords.
'PasswordMinNumber' => 1, // Number of numbers to require in passwords.
'PasswordMinSymbol' => 0, // Number of symbols to require in passwords.
'GMMinPasswordLength' => 8, // Minimum password length for GM accounts.
'GMPasswordMinUpper' => 1, // Number of upper-case letters to require in passwords for GM accounts.
'GMPasswordMinLower' => 1, // Number of lower-case letters to require in passwords for GM accounts.
'GMPasswordMinNumber' => 1, // Number of numbers to require in passwords for GM accounts.
'GMPasswordMinSymbol' => 1, // Number of symbols to require in passwords for GM accounts.
'RandomPasswordLength' => 16, // This is the length of the random password generated by the "Reset Password" feature. (NOTE: Hardcoded minimum value of 8)
'AllowUserInPassword' => false, // Whether or not to allow the password to contain the username. (NOTE: A case-insensitive search is performed)
'AllowDuplicateEmails' => false, // Whether or not to allow duplicate e-mails to be used in registration. (See Mailer config options)
'RequireEmailConfirm' => false, // Require e-mail confirmation during registration.
'RequireChangeConfirm' => false, // Require confirmation when changing e-mail addresses.
'EmailConfirmExpire' => 48, // E-mail confirmations expire hours. Unconfirmed accounts will expire after this period of time.
'PincodeEnabled' => true, // Whether or not the pincode system is enabled in your server. (Check your char_athena.conf file. Enabled by default.)
'MailerFromAddress' => 'noreply@localhost', // The e-mail address displayed in the From field.
'MailerFromName' => 'MailerName', // The name displayed with the From e-mail address.
'MailerUseSMTP' => false, // Whether or not to use a separate SMTP server for sending mail.
'MailerSMTPUseSSL' => false, // Whether or not mailer should connect using SSL (yes for GMail).
'MailerSMTPUseTLS' => false, // Same as above SSL setting, but for TLS. This setting will override the SSL setting.
'MailerSMTPPort' => null, // When MailerUseSMTP is true: SMTP server port (mailer will default to 25).
'MailerSMTPHosts' => null, // When MailerUseSMTP is true: A string host or array of hosts (e.g., 'host1' or array('host1', 'backuphost')).
'MailerSMTPUsername' => null, // When MailerUseSMTP is true: Authorized username for SMTP server.
'MailerSMTPPassword' => null, // When MailerUseSMTP is true: Authorized password for SMTP server (for above user).
'ServerStatusCache' => 2, // Store a cached server status and refresh every X minutes. Default: 2 minutes (value is measured in minutes).
'ServerStatusTimeout' => 2, // For each server, spend X amount of seconds to determine whether it's up or not.
'SessionKey' => 'fluxSessionData', // Shouldn't be changed, just specifies the session key to be used for session data.
'DefaultModule' => 'main', // This is the module to execute when none has been specified.
'DefaultAction' => 'index', // This is the default action for any module, probably should leave this alone. (Deprecated)
'GzipCompressOutput' => false, // Whether or not to compress output using zlib.
'GzipCompressionLevel' => 9, // zlib compression level. (1~9)
'OutputCleanHTML' => true, // Use this if you have Tidy installed to clean your HTML output when serving pages.
'ShowCopyright' => true, // Whether or not to show the copyright footer.
'ShowRenderDetails' => true, // Shows the "page rendered in X seconds" and "number of queries executed: X" in the default theme.
'UseCleanUrls' => false, // Set to true if you're running Apache and it supports mod_rewrite and .htaccess files.
'DebugMode' => false, // Set to false to minimize technical details from being output by Flux. WARNING: DO NOT USE THIS OPTION ON A PUBLICALLY-ACCESSIBLE CP.
'UseCaptcha' => false, // Use CAPTCHA image for account registration to prevent automated account creations. (Requires GD2/FreeType2)
'UseLoginCaptcha' => false, // Use CAPTCHA image for account logins. (Requires GD2/FreeType2)
'EnableReCaptcha' => false, // Enables the use of reCAPTCHA instead of Flux's native GD2 library (http://www.google.com/recaptcha)
'ReCaptchaPublicKey' => '...', // This is your reCAPTCHA public key [REQUIRED FOR RECAPTCHA] (sign up at http://www.google.com/recaptcha)
'ReCaptchaPrivateKey' => '...', // This is your reCAPTCHA private key [REQUIRED FOR RECAPTCHA] (sign up at http://www.google.com/recaptcha)
'ReCaptchaTheme' => 'light', // ReCaptcha theme to use (Value: dark or light) (see: https://developers.google.com/recaptcha/docs/display#render_param)
'DisplaySinglePages' => true, // Whether or not to display paging for single page results.
'ForwardYears' => 15, // (Visual) The number of years to display ahead of the current year in date inputs.
'BackwardYears' => 60, // (Visual) The number of years to display behind the current year in date inputs.
'ColumnSortAscending' => ' ▲', // (Visual) Text displayed for ascending sorted column names.
'ColumnSortDescending' => ' ▼', // (Visual) Text displayed for descending sorted column names.
'DisplayCashPoints' => false, // Whether or not to display "Cash Points" instead of the player's "Credits" in the control panel.
'CreditExchangeRate' => 1.0, // The rate at which credits are exchanged for dollars.
'MinDonationAmount' => 2.0, // Minimum donation amount. (NOTE: Actual donations made that are less than this account won't be exchanged)
'DonationCurrency' => 'USD', // Preferred donation currency. Only donations made in this currency will be processed for credit deposits.
'MoneyDecimalPlaces' => 2, // (Visual) Number of decimal places to display in amount.
'MoneyThousandsSymbol' => ',', // (Visual) Thousandths place separator (a period in European currencies).
'MoneyDecimalSymbol' => '.', // (Visual) Decimal separator (a comma in European currencies).
'AcceptDonations' => true, // Whether or not to accept donations.
'PayPalIpnUrl' => 'www.paypal.com', // The ipnpb.paypal.com and ipnpb.sandbox.paypal.com endpoints only accept HTTPS connections. If you currently use www.paypal.com, you should move to ipnpb.paypal.com when you update your code to use HTTPS.
'PayPalBusinessEmail' => 'admin@localhost', // Enter the e-mail under which you have registered your business account.
'PayPalReceiverEmails' => array( // These are the receiver e-mail addresses who are allowed to receive payment.
//'admin2@localhost', // -- This array may be empty if you only use one e-mail
//'admin3@localhost' // -- because your Business Email is also checked.
),
'PaypalHackNotify' => true, // Send email notification if hack attempt detected (Notification will be send for each address in list PayPalBusinessEmail and PayPalReceiverEmails)
'PayPalAllowedHosts' => array( // PayPal IP list https://www.paypal.com/fm/smarthelp/article/what-are-the-ip-addresses-for-live-paypal-servers-ts1056
'ipn.sandbox.paypal.com',
'notify.paypal.com',
'66.211.170.66',
'173.0.81.1',
'173.0.81.0/24',
'173.0.81.33',
'173.0.81.65',
'173.0.81.140',
'64.4.240.0/21',
'64.4.248.0/22',
'6.211.168.0/22',
'173.0.80.0/20',
'91.243.72.0/23'
),
'GStorageLeaderOnly' => false, // Only allow guild leader to view guild storage rather than all members?
'DivorceKeepChild' => false, // Keep child after divorce?
'DivorceKeepRings' => false, // Keep wedding rings after divorce?
'IpWhitelistPattern' => // PCRE Format Pattern. It's recommended you add your gameserver, webserver and server owner's IPs here.
'(127\.0\.0\.1|0(\.[0\*]){3})', // WARNING: This string isn't escaped so be careful which chars you use!
// By default, whitelists 127.0.0.1 (localhost) and 0.0.0.0 (all interfaces; whitelists all wildcard bans that can achive this too)
'AllowIpBanLogin' => false, // Allow logging into account from banned IP.
'AllowTempBanLogin' => false, // Allow logging in of temporarily banned accounts.
'AllowPermBanLogin' => false, // Allow logging in of permanently banned accounts.
'AutoRemoveTempBans' => true, // Automatically remove expired temporary bans on certain pages.
'ItemShopMaxCost' => 99, // Max price an item can be sold for.
'ItemShopMaxQuantity' => 99, // Max quantity the item may be sold at once for.
'ItemShopItemPerPage' => 5, // The number of items to display per page in the "Item Shop" page.
'ShowItemDesc' => false, // Displays generated item descs from parsed itemInfo.lua
'HideFromWhosOnline' => AccountLevel::LOWGM, // Levels greater than or equal to this will be hidden from the "Who's Online" page.
'HideFromMapStats' => AccountLevel::LOWGM, // Levels greater than or equal to this will be hidden from the "Map Stats" page.
'EnableGMPassSecurity' => AccountLevel::LOWGM, // Levels greater than or equal to this will be required to use passwords that meet the earlier GM Password settings.
'ChargeGenderChange' => 0, // You can specify this as the number of credits to charge for gender change. Can be 0 for free change.
'BanPaymentStatuses' => array( // Payment statuses that will automatically ban the account owner if received.
'Cancelled_Reversal', // -- 'Cancelled_Reversal'
'Reversed', // -- 'Reversed'
),
'HoldUntrustedAccount' => 0, // This is the time in hours to hold a donation crediting process for, if the account
// isn't a trusted account. Specify 0 or false to disable this feature.
'AutoUnholdAccount' => false, // Enable this to auto-unhold an account and credit it if the transaction is still
// valid. This only applies if you are using the HoldUnstrustedAccount feature.
// If you want to run a cron job instead, you can make a request to the '/donate/update'
// module/action with the InstallerPassword as the password to run the update task.
// With clean URLs: http:////donate/update?password=
// Without clean URLs: http:///?module=donate&action=update&password=
// NOTE: This option is HIGHLY inefficient, it's recommended to run a cron job instead.
'AutoPruneAccounts' => false, // Enable this to automatically prune expired accounts. Enabling this is a performance
// performance killer. See 'AutoUnholdAccount' for running this task as a cron job,
// the module is 'account' and the action is 'prune'.
// With clean URLs: http:////account/prune?password=
// Without clean URLs: http:///?module=account&action=prune&password=
'ShopImageExtensions' => array( // These are the image extensions allowed for uploading in the item shop.
'png', 'jpg', 'gif', 'bmp', 'jpeg'
),
'NoResetPassGroupLevel' => AccountLevel::LOWGM, // Minimum group level of account to prevent password reset using control panel.
'CharRankingLimit' => 20, //
'GuildRankingLimit' => 20, //
'ZenyRankingLimit' => 20, //
'DeathRankingLimit' => 20, //
'AlchemistRankingLimit' => 20, //
'BlacksmithRankingLimit' => 20, //
'HomunRankingLimit' => 20, //
'MVPRankingLimit' => 20, //
'RankingHideGroupLevel' => AccountLevel::LOWGM, //
'InfoHideZenyGroupLevel' => AccountLevel::LOWGM, // Minimum group level of account to hide zeny from in server information page.
'CharRankingThreshold' => 0, // Number of days the character must have logged in within to be listed in character ranking. (0 = disabled)
'ZenyRankingThreshold' => 0, // Number of days the character must have logged in within to be listed in zeny ranking. (0 = disabled)
'DeathRankingThreshold' => 0, // Number of days the character must have logged in within to be listed in death ranking. (0 = disabled)
'AlchemistRankingThreshold' => 0, // Number of days the character must have logged in within to be listed in death ranking. (0 = disabled)
'HomunRankingThreshold' => 0, // Number of days the character must have logged in within to be listed in homunculus ranking. (0 = disabled)
'HideTempBannedCharRank' => false, // Hide temporarily banned characters from ranking.
'HidePermBannedCharRank' => true, // Hide permanently banned characters from ranking.
'HideTempBannedZenyRank' => false, // Hide temporarily banned characters from ranking.
'HidePermBannedZenyRank' => true, // Hide permanently banned characters from ranking.
'HideTempBannedDeathRank' => false, // Hide temporarily banned characters from ranking.
'HidePermBannedDeathRank' => true, // Hide permanently banned characters from ranking.
'HideTempBannedAlcheRank' => false, // Hide temporarily banned characters from ranking.
'HidePermBannedAlcheRank' => true, // Hide permanently banned characters from ranking.
'HideTempBannedSmithRank' => false, // Hide temporarily banned characters from ranking.
'HidePermBannedSmithRank' => true, // Hide permanently banned characters from ranking.
'HideTempBannedStats' => false, // Hide temporarily banned accounts from statistics.
'HidePermBannedStats' => true, // Hide permanently banned accounts from statistics.
'HideTempBannedHomunRank' => false, // Hide temporarily banned characters from ranking.
'HidePermBannedHomunRank' => true, // Hide permanently banned characters from ranking.
'SortJobsByAmount' => false, // Sort job class information on statistics page by descending quantity (false = Sort by Job ID).
'CpLoginLogShowPassword' => false, // Show password in CP login log (also see access.php's SeeCpLoginLogPass).
'CpResetLogShowPassword' => false, // Show password in CP "password resets" log (also see access.php's SeeCpResetPass).
'CpChangeLogShowPassword' => false, // Show password in CP "password changes" log (also see access.php's SeeCpChangePass).
'AdminMenuNewStyle' => true, // Use new-style admin menu; Applies to 'default' theme.
'EnablePeakDisplay' => true, // Display Peak User count on Server Status page.
// News Options
'CMSNewsOnHomepage' => true, // Display News on Home Page instead of "You've Just Installed FluxCP" message?
'CMSNewsType' => 1, // Type = source of news feed:
// 1 = Built-in news page
// 2 = RSS Import
'CMSNewsRSS' => 'https://rathena.org/board/rss/1-latest-community-announcements.xml/', // Use if CMSNewsType = 2
'CMSNewsLimit' => 4, // Number of news items to display
'CMSDisplayModifiedBy' => false, // If a news item has been modified, display modified date under news item?
// Service Desk
'StaffReplyColour' => 'brown',
'FontResolvedColour' => 'green',
'FontPendingColour' => 'orange',
'FontClosedColour' => 'darkgrey',
'SDEnableCreditRewards' => true, // Show option in Service Desk to reward player X credits for reporting bugs/abuse/etc.
'SDCreditReward' => 10, // Number of credits to award account.
// Discord Webhooks
'DiscordUseWebhook' => false,
'DiscordWebhookURL' => 'enter_webhook_url_from_discord_here',
'DiscordSendOnRegister' => true, // Sends a channel message when someone registers
'DiscordSendOnNewTicket' => true, // Sends a channel message when someone submits a new ticket to the Service Desk
'DiscordSendOnWebCommand' => true, // Sends a channel message when someone uses the Web Command feature in FluxCP
'DiscordSendOnMarketing' => true, // Sends a channel message when someone uses the Send Email feature in FluxCP
'DiscordSendOnErrorException' => true, // Sends a channel message when an exception is thrown
'TinyMCEKey' => 'no-key', // Register for a key at https://www.tiny.cloud/my-account/dashboard/
// These are the main menu items that should be displayed by themes.
// They route to modules and actions. Whether they are displayed or
// not at any given time depends on the user's account group level and/or
// their login status.
'MenuItems' => array(
'MainMenuLabel' => array(
'HomeLabel' => array('module' => 'main'),
//'ForumLabel' => array('exturl' => 'http://www.fluxro.com/community'), // External forum link
//'ForumLabel' => array('module' => 'forums'), // Built-in forum link
'NewsLabel' => array('module' => 'news'),
// Sample items for pages function.
'DownloadsLabel' => array('module' => 'pages','action'=>'content','param'=>array('path'=>'downloads')),
'RulesLabel' => array('module' => 'pages','action'=>'content','param'=>array('path'=>'rules')),
// End sample items for pages function.
),
'AccountLabel' => array(
'AccountCreateHeading' => array('module' => 'account', 'action' => 'create'),
'LoginTitle' => array('module' => 'account', 'action' => 'login'),
'MyAccountLabel' => array('module' => 'account', 'action' => 'view'),
'HistoryLabel' => array('module' => 'history'),
'ServiceDeskLabel' => array('module' => 'servicedesk'),
'LogoutTitle' => array('module' => 'account', 'action' => 'logout'),
),
'DonationsLabel' => array(
'PurchaseLabel' => array('module' => 'purchase'),
'DonateLabel' => array('module' => 'donate'),
),
'InformationLabel' => array(
'ServerInfoLabel' => array('module' => 'server', 'action' => 'info'),
'ServerStatusLabel' => array('module' => 'server', 'action' => 'status'),
'WoeHoursLabel' => array('module' => 'woe'),
'CastlesLabel' => array('module' => 'castle'),
'WhosOnlineLabel' => array('module' => 'character', 'action' => 'online'),
'MapStaticsLabel'=> array('module' => 'character', 'action' => 'mapstats'),
'RankingInfoLabel' => array('module' => 'ranking', 'action' => 'character'),
'VendingInfoLabel' => array('module' => 'vending'),
'BuyingstoreInfoLabel' => array('module' => 'buyingstore'),
),
'DatabaseLabel' => array(
'ItemDatabaseLabel' => array('module' => 'item'),
'MobDatabaseLabel' => array('module' => 'monster'),
),
'SocialLabel' => array(
'JoinUsInFacebookLabel' => array('exturl' => 'https://www.facebook.com/'),
'RateUsOnRMSLabel' => array('exturl' => ''),
),
'Service Desk' => array(
'ServiceDeskLabel' => array('module' => 'servicedesk', 'action' => 'staffindex'),
),
'Misc. Stuff' => array(
'AccountLabel' => array('module' => 'account'),
'CharacterLabel' => array('module' => 'character'),
'CPLogsLabel' => array('module' => 'cplog'),
'PagesLabel' => array('module' => 'pages'),
'NewsLabel' => array('module' => 'news', 'action' => 'manage'),
'GuildsLabel' => array('module' => 'guild'),
'IPBanListLabel' => array('module' => 'ipban'),
'rALogsLabel' => array('module' => 'logdata'),
'ReInstallLabel' => array('module' => 'install', 'action' => 'reinstall'),
'SendMailLabel' => array('module' => 'mail'),
'WCTitleLabel' => array('module' => 'webcommands'),
//'Auction' => array('module' => 'auction'),
//'Economy' => array('module' => 'economy')
)
),
// Sub-menu items that are displayed for any action belonging to a
// particular module. The format it simple.
'SubMenuItems' => array(
'history' => array(
'gamelogin' => 'Game Logins',
'cplogin' => 'CP Logins',
'emailchange' => 'E-Mail Changes',
'passchange' => 'Password Changes',
'passreset' => 'Password Resets'
),
'account' => array(
'index' => 'List Accounts',
'view' => 'View Account',
'changepass' => 'Change Password',
'changemail' => 'Change E-mail',
'changesex' => 'Change Gender',
'transfer' => 'Transfer Credits',
'xferlog' => 'Credit Transfer History',
'cart' => 'Go to Shopping Cart',
'login' => 'Login',
'create' => 'Register',
'resetpass' => 'Reset Password',
'resend' => 'Resend E-mail Confirmation'
),
'guild' => array(
'index' => 'List Guilds',
'export' => 'Export Guild Emblems'
),
'server' => array(
'status' => 'View Status',
'status-xml' => 'View Status as XML'
),
'logdata' => array(
'branch' => 'Branches',
'char' => 'Characters',
'cashpoints' => 'CashPoints',
'chat' => 'Chat Messages',
'command' => 'Commands',
'feeding' => 'Feeding',
'inter' => 'Interactions',
'pick' => 'Item Picks',
'login' => 'Logins',
'mvp' => 'MVP',
'npc' => 'NPC',
'zeny' => 'Zeny'
),
'cplog' => array(
'paypal' => 'PayPal Transactions',
'create' => 'Account Registrations',
'login' => 'Logins',
'resetpass' => 'Password Resets',
'changepass' => 'Password Changes',
'changemail' => 'E-mail Changes',
'ban' => 'Account Bans',
'ipban' => 'IP Bans'
),
'purchase' => array(
'index' => 'Shop',
'cart' => 'Go to Cart',
'checkout' => 'Checkout',
'clear' => 'Empty Cart',
'pending' => 'Pending Redemption'
),
'donate' => array(
'index' => 'Make a Donation',
'history' => 'Donation History',
'trusted' => 'Trusted PayPal E-mails'
),
'ipban' => array(
'index' => 'IP Ban List',
'add' => 'Add IP Ban'
),
'ranking' => array(
'character' => 'Characters',
'death' => 'Deaths',
'alchemist' => 'Alchemists',
'blacksmith' => 'Blacksmiths',
'homunculus' => 'Homunculus',
'mvp' => 'MVPs',
'guild' => 'Guilds',
'zeny' => 'Zeny'
),
'item' => array(
'index' => 'List Items',
'iteminfo' => 'Add Item Info',
),
'pages' => array(
'index' => 'Manage Pages',
'add' => 'Add New Page',
),
'news' => array(
'index' => 'Latest News',
'manage' => 'Manage',
'add' => 'Add News',
),
'servicedesk' => array(
'staffindex' => 'View Active',
'staffviewclosed'=> 'View Closed',
'staffsettings' => 'Staff Settings',
'catcontrol' => 'Category Control',
),
'vending' => array(
'index' => 'Vendors',
),
'buyingstore' => array(
'index' => 'Buyers',
),
),
'AllowMD5PasswordSearch' => false,
'ReallyAllowMD5PasswordSearch' => false, // Are you POSITIVELY sure?
// Specifies which modules and actions should be ignored by Tidy
// (enabled/disabled by the OutputCleanHTML option).
'TidyIgnore' => array(
array('module' => 'captcha'),
array('module' => 'guild', 'action' => 'emblem')
),
// Job classes, loaded from another file to avoid cluttering this one.
// There isn't normally a need to modify this file, unless it's been
// modified in an update. (In English: DON'T TOUCH THIS.)
'JobClasses' => include('jobs.php'),
// Alchemist job classes, mostly used for alchemist rankings.
// Should be left alone unless new alchemist-related job classes are introduced.
'AlchemistJobClasses' => include('jobs_alchemist.php'),
// Blacksmith job classes, mostly used for blacksmith rankings.
// Should be left alone unless new blacksmith-related job classes are introduced.
'BlacksmithJobClasses' => include('jobs_blacksmith.php'),
// Gender-linked Job class IDs and their corresponding names.
// Should be left alone unless new gender-specific job classes are introduced.
'GenderLinkedJobClasses' => include('jobs_gender_linked.php'),
// Homunculus class IDs and their corresponding names.
// Best not to mess with this either.
'HomunClasses' => include('homunculus.php'),
// Item Types with their corresponding names.
// Shouldn't touch this either.
'ItemTypes' => include('itemtypes.php'),
// Item SubTypes with their corresponding names.
// Shouldn't touch this either.
'ItemSubTypes' => include('itemsubtypes.php'),
// Common Equip Location Combinations with their corresponding names.
// Shouldn't touch this unless you've added custom combinations.
'EquipLocationCombinations' => include('equip_location_combinations.php'),
// Error Code -> Error Type mapping.
// Shouldn't need touching, however modifying loginerrors.php should be relatively safe.
'LoginErrors' => include('loginerrors.php'),
// rA equip jobs mapping.
'EquipJobs' => include('equip_jobs.php'),
// rA equip locations mapping.
'EquipLocations' => include('equip_locations.php'),
// rA equip upper mapping.
'EquipUpper' => include('equip_upper.php'),
// rA monster sizes mapping.
'MonsterSizes' => include('sizes.php'),
// rA monster races mapping.
'MonsterRaces' => include('races.php'),
// rA elements mapping.
'Elements' => include('elements.php'),
// rA attributes mapping.
'Attributes' => include('attributes.php'),
// rA monster modes mapping.
'MonsterModes' => include('monstermode.php'),
'MonsterAI' => include('monster_ai.php'),
// Item shop categories.
'ShopCategories' => include('shopcategories.php'),
// Item pick and zeny log types.
'PickTypes' => include('picktypes.php'),
// Type of feeding
'FeedingTypes' => include('feedingtypes.php'),
// Castle names.
'CastleNames' => include('castlenames.php'),
// Trade restrictions.
'TradeRestriction' => include('trade_restrictions.php'),
// Item flags.
'ItemFlags' => include('itemsflags.php'),
// Item random options.
'RandomOptions' => include('item_randoptions.php'),
// DON'T TOUCH. THIS IS FOR DEVELOPERS.
'FluxTables' => array(
'CreditsTable' => 'cp_credits',
'CreditTransferTable' => 'cp_xferlog',
'ItemShopTable' => 'cp_itemshop',
'TransactionTable' => 'cp_txnlog',
'RedemptionTable' => 'cp_redeemlog',
'AccountCreateTable' => 'cp_createlog',
'AccountBanTable' => 'cp_banlog',
'IpBanTable' => 'cp_ipbanlog',
'DonationTrustTable' => 'cp_trusted',
'AccountPrefsTable' => 'cp_loginprefs',
'CharacterPrefsTable' => 'cp_charprefs',
'ResetPasswordTable' => 'cp_resetpass',
'ChangeEmailTable' => 'cp_emailchange',
'LoginLogTable' => 'cp_loginlog',
'ChangePasswordTable' => 'cp_pwchange',
'OnlinePeakTable' => 'cp_onlinepeak',
'CMSNewsTable' => 'cp_cmsnews',
'CMSPagesTable' => 'cp_cmspages',
'CMSSettingsTable' => 'cp_cmssettings',
'ServiceDeskTable' => 'cp_servicedesk',
'ServiceDeskATable' => 'cp_servicedeska',
'ServiceDeskCatTable' => 'cp_servicedeskcat',
'ServiceDeskSettingsTable' => 'cp_servicedesksettings',
'WebCommandsTable' => 'cp_commands',
'ItemDescTable' => 'cp_itemdesc',
)
);
?>
================================================
FILE: config/attributes.php
================================================
'Ice',
2 => 'Earth',
3 => 'Fire',
4 => 'Wind',
);
?>
================================================
FILE: config/cashshopcategories.php
================================================
'New',
1 => 'Hot',
2 => 'Limited',
3 => 'Rental',
4 => 'Gear',
5 => 'Buff',
6 => 'Heal',
7 => 'Other',
8 => 'Sale'
);
?>
================================================
FILE: config/castlenames.php
================================================
'Neuschwanstein',
1 => 'Hohenschwangau',
2 => 'Nuenberg',
3 => 'Wuerzburg',
4 => 'Rothenburg',
5 => 'Repherion',
6 => 'Eeyolbriggar',
7 => 'Yesnelph',
8 => 'Bergel',
9 => 'Mersetzdeitz',
10 => 'Bright Arbor',
11 => 'Scarlet Palace',
12 => 'Holy Shadow',
13 => 'Sacred Altar',
14 => 'Bamboo Grove Hill',
15 => 'Kriemhild',
16 => 'Swanhild',
17 => 'Fadhgridh',
18 => 'Skoegul',
19 => 'Gondul',
20 => 'Novice Aldebaran',
21 => 'Novice Geffen',
22 => 'Novice Payon',
23 => 'Novice Prontera',
24 => 'Himinn',
25 => 'Andlangr',
26 => 'Viblainn',
27 => 'Hljod',
28 => 'Skidbladnir',
29 => 'Mardol',
30 => 'Cyr',
31 => 'Horn',
32 => 'Gefn',
33 => 'Bandis',
34 => 'Leilah',
35 => 'Pavianne',
36 => 'Jasmine',
37 => 'Roxie',
38 => 'Curly Sue',
39 => 'Gaebolg',
40 => 'Richard',
41 => 'Wigner',
42 => 'Heine',
43 => 'Nerious'
// kRO Names
/**
* 0 => 'Noisyubantian',
* 1 => 'Hohensyubangawoo',
* 2 => 'Nyirenverk',
* 3 => 'Byirtsburi',
* 4 => 'Rotenburk',
* 5 => 'Reprion',
* 6 => 'Yolbriger',
* 7 => 'Isinlife',
* 8 => 'Berigel',
* 9 => 'Melsedetsu',
*10 => 'Mingting',
*11 => 'Tiantan',
*12 => 'Fuying',
*13 => 'Honglou',
*14 => 'Zhulinxian',
*15 => 'Creamhilt',
*16 => 'Sbanhealt',
*17 => 'Lazrigees',
*18 => 'Squagul',
*19 => 'Guindull',
*20 => 'Novice Aldebaran',
*21 => 'Novice Geffen',
*22 => 'Novice Payon',
*23 => 'Novice Prontera',
*24 => 'Himinn',
*25 => 'Andlangr',
*26 => 'Viblainn',
*27 => 'Hljod',
*28 => 'Skidbladnir',
*29 => 'Mardol',
*30 => 'Cyr',
*31 => 'Horn',
*32 => 'Gefn',
*33 => 'Bandis',
*34 => 'Kafragarten 1',
*35 => 'Kafragarten 2',
*36 => 'Kafragarten 3',
*37 => 'Kafragarten 4',
*38 => 'Kafragarten 5',
*39 => 'Gloria 1',
*40 => 'Gloria 2',
*41 => 'Gloria 3',
*42 => 'Gloria 4',
*43 => 'Gloria 5'
*/
)
?>
================================================
FILE: config/elements.php
================================================
'Neutral',
'Water' => 'Water',
'Earth' => 'Earth',
'Fire' => 'Fire',
'Wind' => 'Wind',
'Poison' => 'Poison',
'Holy' => 'Holy',
'Dark' => 'Dark',
'Ghost' => 'Ghost',
'Undead' => 'Undead'
);
?>
================================================
FILE: config/equip_jobs.php
================================================
// Default job list
array(
'job_all' => 'All jobs',
'job_novice' => 'Novice',
'job_supernovice' => 'Super novice',
'job_swordman' => 'Swordman',
'job_mage' => 'Mage',
'job_archer' => 'Archer',
'job_acolyte' => 'Acolyte',
'job_merchant' => 'Merchant',
'job_thief' => 'Thief',
'job_knight' => 'Knight',
'job_priest' => 'Priest',
'job_wizard' => 'Wizard',
'job_blacksmith' => 'Blacksmith',
'job_hunter' => 'Hunter',
'job_assassin' => 'Assassin',
'job_crusader' => 'Crusader',
'job_monk' => 'Monk',
'job_sage' => 'Sage',
'job_rogue' => 'Rogue',
'job_alchemist' => 'Alchemist',
'job_barddancer' => 'Bard / Dancer',
'job_taekwon' => 'Taekwon',
'job_stargladiator' => 'Star Gladiator',
'job_soullinker' => 'Soul Linker',
'job_gunslinger' => 'Gunslinger',
'job_ninja' => 'Ninja',
),
1 => // Renewal job list
array(
'job_kagerouoboro' => 'Kagerou / Oboro',
'job_rebellion' => 'Rebellion',
'job_summoner' => 'Summoner'
)
)
?>
================================================
FILE: config/equip_location_combinations.php
================================================
'Two-Handed',
'location_head_low/location_head_mid/location_head_top' => 'Upper/Mid/Lower Headgear',
'location_head_mid/location_head_top' => 'Upper/Mid Headgear',
'location_head_top/location_head_low' => 'Upper/Lower Headgear',
'location_head_low/location_head_mid' => 'Mid/Lower Headgear',
'location_head_low/location_head_top' => 'Upper/Lower Headgear',
'location_costume_head_mid/location_costume_head_top' => 'Costume Upper/Mid Headgear',
'location_costume_head_low/location_costume_head_top' => 'Costume Upper/Lower Headgear',
'location_costume_head_low/location_costume_head_mid' => 'Costume Mid/Lower Headgear',
'location_costume_head_low/location_costume_head_mid/location_costume_head_top' => 'Costume Upper/Mid/Lower Headgear',
'location_left_accessory/location_right_accessory' => 'Accessory Left/Right',
'location_armor/location_garment/location_head_low/location_head_mid/location_head_top/location_left_accessory/location_left_hand/location_right_accessory/location_right_hand/location_shoes' => 'All equip',
)
?>
================================================
FILE: config/equip_locations.php
================================================
'Lower Headgear',
'location_right_hand' => 'Main Hand',
'location_garment' => 'Garment',
'location_right_accessory' => 'Accessory Right',
'location_armor' => 'Armor',
'location_left_hand' => 'Off Hand',
'location_shoes' => 'Footgear',
'location_left_accessory' => 'Accessory Left',
'location_head_top' => 'Upper Headgear',
'location_head_mid' => 'Middle Headgear',
'location_costume_head_top' => 'Costume Top Headgear',
'location_costume_head_mid' => 'Costume Mid Headgear',
'location_costume_head_low' => 'Costume Low Headgear',
'location_costume_garment' => 'Costume Garment',
'location_ammo' => 'Ammo',
'location_shadow_armor' => 'Shadow Armor',
'location_shadow_weapon' => 'Shadow Weapon',
'location_shadow_shield' => 'Shadow Shield',
'location_shadow_shoes' => 'Shadow Shoes',
'location_shadow_right_accessory' => 'Shadow Accessory Right (Earring)',
'location_shadow_left_accessory' => 'Shadow Accessory Left (Pendant)',
)
?>
================================================
FILE: config/equip_upper.php
================================================
// Default class list
array(
'class_all' => 'All classes',
'class_normal' => 'Normal',
'class_upper' => 'Upper',
'class_baby' => 'Baby'
),
1 => // Renewal class list
array(
'class_third' => 'Third',
'class_third_upper' => 'Third Upper',
'class_third_baby' => 'Third Baby'
)
)
?>
================================================
FILE: config/error.php
================================================
================================================
FILE: config/feedingtypes.php
================================================
'Pet',
'H' => 'Homunculus',
'O' => 'Other',
);
================================================
FILE: config/groups.php
================================================
=> array(
* 'name' => "",
* 'level' => "",
* ),
*/
0 => array(
'name' => "Player",
'level' => AccountLevel::NORMAL
),
1 => array(
'name' => "Super Player",
'level' => AccountLevel::NORMAL
),
2 => array(
'name' => "Support",
'level' => AccountLevel::LOWGM
),
3 => array(
'name' => "Script Manager",
'level' => AccountLevel::LOWGM
),
4 => array(
'name' => "Event Manager",
'level' => AccountLevel::LOWGM
),
5 => array(
'name' => "VIP",
'level' => AccountLevel::NORMAL
),
10 => array(
'name' => "Law Enforcement",
'level' => AccountLevel::HIGHGM
),
99 => array(
'name' => "Admin",
'level' => AccountLevel::ADMIN
)
);
// DON'T TOUCH ANYTHING BELOW. THIS IS FOR DEVELOPERS.
/**
* Get array of all groups.
*
* @return array
* @access public
*/
public static function getArray() {
return self::$groups;
}
/**
* Get array of group IDs that satisfy the operation
* condition that compares the group level.
*
* @param int $compare
* @param string $op
* @return array
* @access public
*/
public static function getGroupID($compare, $op) {
$group_id = array();
foreach(self::$groups as $id => $group) {
if( ($op == '<' && $group['level'] < $compare) || ($op == '>' && $group['level'] > $compare) ||
($op == '<=' && $group['level'] <= $compare) || ($op == '>=' && $group['level'] >= $compare)) {
array_push($group_id, $id);
}
}
return $group_id;
}
/**
* Get the level associated with the group ID.
*
* @param int $group_id
* @return int
* @access public
*/
public static function getGroupLevel($group_id) {
if(isset(self::$groups[$group_id]['level'])) {
return self::$groups[$group_id]['level'];
}
else {
return AccountLevel::NORMAL;
}
}
/**
* Get the name associated with the group ID.
*
* @param int $group_id
* @return string
* @access public
*/
public static function getGroupName($group_id) {
if(isset(self::$groups[$group_id]['name'])) {
return self::$groups[$group_id]['name'];
}
else {
return "N/A";
}
}
}
?>
================================================
FILE: config/homunculus.php
================================================
'Lif',
6009 => 'Lif',
6005 => 'Lif',
6013 => 'Lif',
// Amistr
6002 => 'Amistr',
6010 => 'Amistr',
6006 => 'Amistr',
6014 => 'Amistr',
// Filir
6003 => 'Filir',
6011 => 'Filir',
6007 => 'Filir',
6015 => 'Filir',
// Vanilmirth
6004 => 'Vanilmirth',
6012 => 'Vanilmirth',
6008 => 'Vanilmirth',
6016 => 'Vanilmirth',
// Homunculus S
6048 => 'Eira',
6049 => 'Bayeri',
6050 => 'Sera',
6051 => 'Dieter',
6052 => 'Elanor'
);
?>
================================================
FILE: config/item_randoptions.php
================================================
'MaxHP +%s',
2 => 'MaxSP +%s',
3 => 'STR +%s',
4 => 'AGI +%s',
5 => 'VIT +%s',
6 => 'INT +%s',
7 => 'DEX +%s',
8 => 'LUK +%s',
9 => 'MaxHP +%s%%',
10 => 'MaxSP +%s%%',
11 => 'HP regen +%s%%',
12 => 'SP regen +%s%%',
13 => 'ATK +%s%%',
14 => 'MATK +%s%%',
15 => 'ASPD +%s',
16 => 'Delay after attack -%s%%',
17 => 'ATK +%s',
18 => 'HIT +%s',
19 => 'MATK +%s',
20 => 'DEF +%s',
21 => 'MDEF +%s',
22 => 'FLEE +%s',
23 => 'Perfect dodge +%s',
24 => 'CRIT +%s',
25 => 'Neutral elemental resistance +%s%%',
26 => 'Water elemental resistance +%s%%',
27 => 'Earth elemental resistance +%s%%',
28 => 'Fire elemental resistance +%s%%',
29 => 'Wind elemental resistance +%s%%',
30 => 'Poison elemental resistance +%s%%',
31 => 'Holy elemental resistance +%s%%',
32 => 'Shadow elemental resistance +%s%%',
33 => 'Ghost elemental resistance +%s%%',
34 => 'Undead elemental resistance +%s%%',
35 => 'All elementals resistance +%s%%',
36 => 'Neutral monster resistance +%s%%',
37 => 'ATK +%s%% against Neutral monster',
38 => 'Water monster resistance +%s%%',
39 => 'ATK +%s%% against Water monster',
40 => 'Earth monster resistance +%s%%',
41 => 'ATK +%s%% against Earth monster',
42 => 'Fire monster resistance +%s%%',
43 => 'ATK +%s%% against Fire monster',
44 => 'Wind monster resistance +%s%%',
45 => 'ATK +%s%% against Wind monster',
46 => 'Poison monster resistance +%s%%',
47 => 'ATK +%s%% against Poison monster',
48 => 'Holy monster resistance +%s%%',
49 => 'ATK +%s%% against Holy monster',
50 => 'Shadow monster resistance +%s%%',
51 => 'ATK +%s%% against Shadow monster',
52 => 'Ghost monster resistance +%s%%',
53 => 'ATK +%s%% against Ghost monster',
54 => 'Undead monster resistance +%s%%',
55 => 'ATK +%s%% against Undead monster',
56 => 'Neutral monster magic resistance +%s%%',
57 => 'MATK +%s%% against Neutral monster',
58 => 'Water monster magic resistance +%s%%',
59 => 'MATK +%s%% against Water monster',
60 => 'Earth monster magic resistance +%s%%',
61 => 'MATK +%s%% against Earth monster',
62 => 'Fire monster magic resistance +%s%%',
63 => 'MATK +%s%% against Fire monster',
64 => 'Wind monster magic resistance +%s%%',
65 => 'MATK +%s%% against Wind monster',
66 => 'Poison monster magic resistance +%s%%',
67 => 'MATK +%s%% against Poison monster',
68 => 'Holy monster magic resistance +%s%%',
69 => 'MATK +%s%% against Holy monster',
70 => 'Shadow monster magic resistance +%s%%',
71 => 'MATK +%s%% against Shadow monster',
72 => 'Ghost monster magic resistance +%s%%',
73 => 'MATK +%s%% against Ghost monster',
74 => 'Undead monster magic resistance +%s%%',
75 => 'MATK +%s%% against Undead monster',
76 => 'Armor element: Neutral',
77 => 'Armor element: Water',
78 => 'Armor element: Earth',
79 => 'Armor element: Fire',
80 => 'Armor element: Wind',
81 => 'Armor element: Poison',
82 => 'Armor element: Holy',
83 => 'Armor element: Shadow',
84 => 'Armor element: Ghost',
85 => 'Armor element: Undead',
//86 => '',
87 => 'Formless monster resistance +%s%%',
88 => 'Undead monster resistance +%s%%',
89 => 'Brute monster resistance +%s%%',
90 => 'Plant monster resistance +%s%%',
91 => 'Insect monster resistance +%s%%',
92 => 'Fish monster resistance +%s%%',
93 => 'Demon monster resistance +%s%%',
94 => 'Demihuman monster resistance +%s%%',
95 => 'Angel monster resistance +%s%%',
96 => 'Dragon monster resistance +%s%%',
97 => 'ATK +%s%% against Formless monster',
98 => 'ATK +%s%% against Undead monster',
99 => 'ATK +%s%% against Brute monster',
100 => 'ATK +%s%% against Plant monster',
101 => 'ATK +%s%% against Insect monster',
102 => 'ATK +%s%% against Fish monster',
103 => 'ATK +%s%% against Demon monster',
104 => 'ATK +%s%% against Demihuman monster',
105 => 'ATK +%s%% against Angel monster',
106 => 'ATK +%s%% against Dragon monster',
107 => 'MATK +%s%% against Formless monster',
108 => 'MATK +%s%% against Undead monster',
109 => 'MATK +%s%% against Brute monster',
110 => 'MATK +%s%% against Plant monster',
111 => 'MATK +%s%% against Insect monster',
112 => 'MATK +%s%% against Fish monster',
113 => 'MATK +%s%% against Devil monster',
114 => 'MATK +%s%% against Demihuman monster',
115 => 'MATK +%s%% against Angel monster',
116 => 'MATK +%s%% against Dragon monster',
117 => 'CRIT +%s against Formless monster',
118 => 'CRIT +%s against Undead monster',
119 => 'CRIT +%s against Brute monster',
120 => 'CRIT +%s against Plant monster',
121 => 'CRIT +%s against Insect monster',
122 => 'CRIT +%s against Fish monster',
123 => 'CRIT +%s against Demon monster',
124 => 'CRIT +%s against Demihuman monster',
125 => 'CRIT +%s against Angel monster',
126 => 'CRIT +%s against Dragon monster',
127 => 'Pierces %s%% DEF of Formless monster',
128 => 'Pierces %s%% DEF of Undead monster',
129 => 'Pierces %s%% DEF of Brute monster',
130 => 'Pierces %s%% DEF of Plant monster',
131 => 'Pierces %s%% DEF of Insect monster',
132 => 'Pierces %s%% DEF of Fish monster',
133 => 'Pierces %s%% DEF of Demon monster',
134 => 'Pierces %s%% DEF of Demihuman monster',
135 => 'Pierces %s%% DEF of Angel monster',
136 => 'Pierces %s%% DEF of Dragon monster',
137 => 'Pierces %s%% MDEF of Formless monster',
138 => 'Pierces %s%% MDEF of Undead monster',
139 => 'Pierces %s%% MDEF of Brute monster',
140 => 'Pierces %s%% MDEF of Plant monster',
141 => 'Pierces %s%% MDEF of Insect monster',
142 => 'Pierces %s%% MDEF of Fish monster',
143 => 'Pierces %s%% MDEF of Demon monster',
144 => 'Pierces %s%% MDEF of Demihuman monster',
145 => 'Pierces %s%% MDEF of Angel monster',
146 => 'Pierces %s%% MDEF of Dragon monster',
147 => 'ATK +%s%% against Normal monster',
148 => 'ATK +%s%% against Boss monster',
149 => 'Normal monster resistance +%s%%',
150 => 'Boss monster resistance +%s%%',
151 => 'MATK +%s%% against Normal monster',
152 => 'MATK +%s%% against Boss monster',
153 => 'Pierces %s%% DEF of Normal monster',
154 => 'Pierces %s%% DEF of Boss monster',
155 => 'Pierces %s%% MDEF of Normal monster',
156 => 'Pierces %s%% MDEF of Boss monster',
157 => 'ATK +%s%% against Small size monster',
158 => 'ATK +%s%% against Medium size monster',
159 => 'ATK +%s%% against Large size monster',
160 => 'Small monster resistance +%s%%',
161 => 'Medium monster resistance +%s%%',
162 => 'Large monster resistance +%s%%',
163 => 'Nullify weapon\'s damage size penalty',
164 => 'Critical attack +%s%%',
165 => 'Critical damage -%s%%',
166 => 'Long range physical attack +%s%%',
167 => 'Long range physical damage -%s%%',
168 => 'Healing skills +%s%%',
169 => 'Restoration gained from Healing skills +%s%%',
170 => 'Variable cast time -%s%%',
171 => 'After cast delay -%s%%',
172 => 'Reduces SP cost by %s%%',
//173 => '',
//174 => '',
175 => 'Weapon element: Neutral',
176 => 'Weapon element: Water',
177 => 'Weapon element: Earth',
178 => 'Weapon element: Fire',
179 => 'Weapon element: Wind',
180 => 'Weapon element: Poison',
181 => 'Weapon element: Holy',
182 => 'Weapon element: Shadow',
183 => 'Weapon element: Ghost',
184 => 'Weapon element: Undead',
185 => 'Indestructible in battle',
186 => 'Indestructible in battle',
187 => 'MATK against Small size monster +%s%%',
188 => 'MATK against Medium size monster +%s%%',
189 => 'MATK against Large size monster +%s%%',
190 => 'Small monster magic resistance +%s%%',
191 => 'Medium monster magic resistance +%s%%',
192 => 'Large monster magic resistance +%s%%',
193 => 'Elemental attacks resistance +%s%%',
194 => 'Formless monster resistance +%s%%',
195 => 'Undead monster resistance +%s%%',
196 => 'Brute monster resistance +%s%%',
197 => 'Plant monster resistance +%s%%',
198 => 'Insect monster resistance +%s%%',
199 => 'Fish monster resistance +%s%%',
200 => 'Demon monster resistance +%s%%',
201 => 'Demihuman monster resistance +%s%%',
202 => 'Angel monster resistance +%s%%',
203 => 'Dragon monster resistance +%s%%',
204 => 'Long range physical attack +%s%%',
205 => 'Long range physical damage -%s%%',
206 => 'Demi-Human players resistance + %s%%',
207 => 'Doram players resistance +%s%%',
208 => 'ATK against Demi-Human players +%s%%',
209 => 'ATK against Doram players +%s%%',
210 => 'MATK against Demi-Human players +%s%%',
211 => 'MATK against Doram players +%s%%',
212 => 'Critical +%s for Demi-Human players',
213 => 'Critical +%s for Doram players',
214 => 'Pierces %s%% DEF of Demi-Human players',
215 => 'Pierces %s%% DEF of Doram players',
216 => 'Pierces %s%% MDEF of Demi-Human players',
217 => 'Pierces %s%% MDEF of Doram players',
218 => 'Recieved reflected damage -%s%%',
219 => 'Melee physical damage +%s%%',
220 => 'Melee physical damage -%s%%',
)
?>
================================================
FILE: config/itemsflags.php
================================================
'Item is available to Buying Stores',
'flag_deadbranch' => 'Item is a Dead Branch type',
'flag_container' => 'Item is part of a Container',
'flag_uniqueid' => 'Item is a unique stack',
'flag_bindonequip' => 'Item is bound to the character upon equipping',
'flag_dropannounce' => 'Item has a special announcement to self on drop',
'flag_noconsume' => 'Item is consumed on use',
)
?>
================================================
FILE: config/itemsubtypes.php
================================================
array(
'1haxe' => 'One-Handed Axe',
'1hspear' => 'One-Handed Spear',
'1hsword' => 'One-Handed Sword',
'2haxe' => 'Two-Handed Axe',
'2hspear' => 'Two-Handed Spear',
'2hstaff' => 'Two-Handed Staff',
'2hsword' => 'Two-Handed Sword',
'book' => 'Book',
'bow' => 'Bow',
'dagger' => 'Dagger',
'gatling' => 'Gatling Gun',
'grenade' => 'Grenade Launcher',
'huuma' => 'Fuuma Shuriken',
'katar' => 'Katar',
'knuckle' => 'Knuckle',
'mace' => 'Mace',
'musical' => 'Musical Instrument',
'revolver' => 'Revolver',
'rifle' => 'Rifle',
'shotgun' => 'Shotgun',
'staff' => 'Staff',
'whip' => 'Whip'
),
'ammo' => array(
'arrow' => 'Arrow',
'bullet' => 'Bullet',
'dagger' => 'Throwing Dagger',
'cannonball' => 'Cannonball',
'grenade' => 'Grenade',
'kunai' => 'Kunai',
'shell' => 'Shell',
'shuriken' => 'Shuriken',
'throwweapon' => 'Throwable Item (Sling Item)'
),
'card' => array(
'normal' => 'Card',
'enchant' => 'Enchant'
)
)
?>
================================================
FILE: config/itemtypes.php
================================================
'Ammo',
'armor' => 'Armor',
'card' => 'Card',
'cash' => 'Cash Shop Reward',
'delayconsume' => 'Delay Consume',
'etc' => 'Etc',
'healing' => 'Healing',
'petarmor' => 'Pet Armor',
'petegg' => 'Pet Egg',
'shadowgear' => 'Shadow Equipment',
'usable' => 'Usable',
'weapon' => 'Weapon'
)
?>
================================================
FILE: config/jobs.php
================================================
'Novice',
1 => 'Swordsman',
2 => 'Mage',
3 => 'Archer',
4 => 'Acolyte',
5 => 'Merchant',
6 => 'Thief',
7 => 'Knight',
8 => 'Priest',
9 => 'Wizard',
10 => 'Blacksmith',
11 => 'Hunter',
12 => 'Assassin',
//13 => 'Knight (Mounted)',
14 => 'Crusader',
15 => 'Monk',
16 => 'Sage',
17 => 'Rogue',
18 => 'Alchemist',
19 => 'Bard',
20 => 'Dancer',
//21 => 'Crusader (Mounted)',
22 => 'Wedding',
23 => 'Super Novice',
24 => 'Gunslinger',
25 => 'Ninja',
26 => 'Xmas',
27 => 'Summer',
28 => 'Hanbok',
29 => 'Oktoberfest',
4001 => 'High Novice',
4002 => 'High Swordsman',
4003 => 'High Mage',
4004 => 'High Archer',
4005 => 'High Acolyte',
4006 => 'High Merchant',
4007 => 'High Thief',
4008 => 'Lord Knight',
4009 => 'High Priest',
4010 => 'High Wizard',
4011 => 'Whitesmith',
4012 => 'Sniper',
4013 => 'Assassin Cross',
//4014 => 'Lord Knight (Mounted)',
4015 => 'Paladin',
4016 => 'Champion',
4017 => 'Professor',
4018 => 'Stalker',
4019 => 'Creator',
4020 => 'Clown',
4021 => 'Gypsy',
//4022 => 'Paladin (Mounted)',
4023 => 'Baby',
4024 => 'Baby Swordsman',
4025 => 'Baby Mage',
4026 => 'Baby Archer',
4027 => 'Baby Acolyte',
4028 => 'Baby Merchant',
4029 => 'Baby Thief',
4030 => 'Baby Knight',
4031 => 'Baby Priest',
4032 => 'Baby Wizard',
4033 => 'Baby Blacksmith',
4034 => 'Baby Hunter',
4035 => 'Baby Assassin',
//4036 => 'Baby Knight (Mounted)',
4037 => 'Baby Crusader',
4038 => 'Baby Monk',
4039 => 'Baby Sage',
4040 => 'Baby Rogue',
4041 => 'Baby Alchemist',
4042 => 'Baby Bard',
4043 => 'Baby Dancer',
//4044 => 'Baby Crusader (Mounted)',
4045 => 'Super Baby',
4046 => 'Taekwon',
4047 => 'Star Gladiator',
//4048 => 'Star Gladiator (Flying)',
4049 => 'Soul Linker',
4050 => 'Jiang Shi',
4051 => 'Death Knight',
4052 => 'Dark Collector',
4054 => 'Rune Knight',
4055 => 'Warlock',
4056 => 'Ranger',
4057 => 'Arch Bishop',
4058 => 'Mechanic',
4059 => 'Guillotine Cross',
4060 => 'Rune Knight+',
4061 => 'Warlock+',
4062 => 'Ranger+',
4063 => 'Arch Bishop+',
4064 => 'Mechanic+',
4065 => 'Guillotine Cross+',
4066 => 'Royal Guard',
4067 => 'Sorcerer',
4068 => 'Minstrel',
4069 => 'Wanderer',
4070 => 'Sura',
4071 => 'Genetic',
4072 => 'Shadow Chaser',
4073 => 'Royal Guard+',
4074 => 'Sorcerer+',
4075 => 'Minstrel+',
4076 => 'Wanderer+',
4077 => 'Sura+',
4078 => 'Genetic+',
4079 => 'Shadow Chaser+',
//4080 => 'Rune Knight (Mounted)',
//4081 => 'Rune Knight+ (Mounted)',
//4082 => 'Royal Guard (Mounted)',
//4083 => 'Royal Guard+ (Mounted)',
//4084 => 'Ranger (Mounted)',
//4085 => 'Ranger+ (Mounted)',
//4086 => 'Mechanic (Magic Gear)',
//4087 => 'Mechanic+ (Magic Gear)',
4096 => 'Baby Rune Knight',
4097 => 'Baby Warlock',
4098 => 'Baby Ranger',
4099 => 'Baby Arch Bishop',
4100 => 'Baby Mechanic',
4101 => 'Baby Guillotine Cross',
4102 => 'Baby Royal Guard',
4103 => 'Baby Sorcerer',
4104 => 'Baby Minstrel',
4105 => 'Baby Wanderer',
4106 => 'Baby Sura',
4107 => 'Baby Genetic',
4108 => 'Baby Shadow Chaser',
//4109 => 'Baby Rune Knight (Mounted)',
//4110 => 'Baby Royal Guard (Mounted)',
//4111 => 'Baby Ranger (Mounted)',
//4112 => 'Baby Mechanic (Magic Gear)',
4190 => 'Expanded Super Novice',
4191 => 'Expanded Super Baby',
4211 => 'Kagerou',
4212 => 'Oboro',
4215 => 'Rebellion',
4218 => 'Summoner',
4220 => 'Baby Summoner',
4222 => 'Baby Ninja',
4223 => 'Baby Kagero',
4224 => 'Baby Oboro',
4225 => 'Baby Taekwon',
4226 => 'Baby Star Gladiator',
4227 => 'Baby Soul Linker',
4228 => 'Baby Gunslinger',
4229 => 'Baby Rebellion',
//4238 => 'Baby Star Gladiator (Union)',
//4238 => 'Baby Star Glad (Union)',
4239 => 'Star Emperor',
4240 => 'Soul Reaper',
4241 => 'Baby Star Emperor',
4242 => 'Baby Soul Reaper',
4252 => 'Dragon Knight',
4253 => 'Meister',
4254 => 'Shadow Cross',
4255 => 'Arch Mage',
4256 => 'Cardinal',
4257 => 'WindHawk',
4258 => 'Imperial Guard',
4259 => 'Biolo',
4260 => 'Abyss Chaser',
4261 => 'Elemental Master',
4262 => 'Inquisitor',
4263 => 'Troubadour',
4264 => 'Trouvere',
//4278 => 'Windhawk (Mounted)',
//4279 => 'Meister (Mounted)',
//4280 => 'Dragon Knight (Mounted)',
//4281 => 'Imperial Guard (Mounted)',
4302 => 'Sky Emperor',
4303 => 'Soul Ascetic',
4304 => 'Shinkiro',
4305 => 'Shiranui',
4306 => 'Night Watch',
4307 => 'Hyper Novice',
4308 => 'Spirit Handler',
//4316 => 'Sky Emperor (Mounted)',
)
?>
================================================
FILE: config/jobs_alchemist.php
================================================
'Alchemist',
4019 => 'Creator',
4041 => 'Baby Alchemist',
4071 => 'Genetic',
4078 => 'Genetic+',
4107 => 'Baby Genetic',
4259 => 'Biolo'
)
?>
================================================
FILE: config/jobs_blacksmith.php
================================================
'Blacksmith',
4011 => 'Whitesmith',
4033 => 'Baby Blacksmith',
4058 => 'Mechanic',
4064 => 'Mechanic+',
4100 => 'Baby Mechanic',
4253 => 'Meister'
)
?>
================================================
FILE: config/jobs_gender_linked.php
================================================
'Bard',
20 => 'Dancer',
4020 => 'Clown',
4021 => 'Gypsy',
4042 => 'Baby Bard',
4043 => 'Baby Dancer',
4068 => 'Minstrel',
4069 => 'Wanderer',
4075 => 'Minstrel+',
4076 => 'Wanderer+',
4104 => 'Baby Minstrel',
4105 => 'Baby Wanderer',
4211 => 'Kagerou',
4212 => 'Oboro',
4223 => 'Baby Kagerou',
4224 => 'Baby Oboro',
4263 => 'Troubadour',
4264 => 'Trouvere'
)
?>
================================================
FILE: config/loginerrors.php
================================================
'Unexpected Error',
1 => 'Invalid Server',
2 => 'Invalid Credentials',
3 => 'Temporarily Banned',
4 => 'Permanently Banned',
5 => 'IP Banned',
6 => 'Invalid Security Code',
7 => 'Pending Confirmation'
)
?>
================================================
FILE: config/monster_ai.php
================================================
array('mode_canattack', 'mode_canmove'),
'02' => array('mode_canattack', 'mode_looter', 'mode_canmove'),
'03' => array('mode_changetargetmelee', 'mode_canattack', 'mode_assist', 'mode_canmove'),
'04' => array('mode_changetargetchase', 'mode_changetargetmelee', 'mode_angry', 'mode_canattack', 'mode_aggressive', 'mode_canmove'),
'05' => array('mode_changetargetchase', 'mode_canattack', 'mode_aggressive', 'mode_canmove'),
'06' => array(),
'07' => array('mode_changetargetmelee', 'mode_canattack', 'mode_assist', 'mode_looter', 'mode_canmove'),
'08' => array('mode_targetweak', 'mode_changetargetchase', 'mode_changetargetmelee', 'mode_canattack', 'mode_aggressive', 'mode_canmove'),
'09' => array('mode_changetargetchase', 'mode_changetargetmelee', 'mode_canattack', 'mode_castsensoridle', 'mode_aggressive', 'mode_canmove'),
'10' => array('mode_canattack', 'mode_aggressive'),
'11' => array('mode_canattack', 'mode_aggressive'),
'12' => array('mode_changetargetchase', 'mode_canattack', 'mode_aggressive', 'mode_canmove'),
'13' => array('mode_changetargetchase', 'mode_changetargetmelee', 'mode_canattack', 'mode_assist', 'mode_aggressive', 'mode_canmove'),
//14 => array(),
//15 => array(),
//16 => array(),
'17' => array('mode_canattack', 'mode_castsensoridle', 'mode_canmove'),
//18 => array(),
'19' => array('mode_changetargetchase', 'mode_changetargetmelee', 'mode_canattack', 'mode_castsensoridle', 'mode_aggressive', 'mode_canmove'),
'20' => array('mode_changetargetchase', 'mode_changetargetmelee', 'mode_castsensorchase', 'mode_canattack', 'mode_castsensoridle', 'mode_aggressive', 'mode_canmove'),
'21' => array('mode_changetargetchase', 'mode_changetargetmelee', 'mode_changechase', 'mode_castsensorchase', 'mode_canattack', 'mode_castsensoridle', 'mode_aggressive', 'mode_canmove'),
//22 => array(),
//23 => array(),
'24' => array('mode_canattack', 'mode_norandomwalk', 'mode_canmove'),
'25' => array('mode_canmove'),
'26' => array('mode_randomtarget', 'mode_changetargetchase', 'mode_changetargetmelee', 'mode_changechase', 'mode_castsensorchase', 'mode_canattack', 'mode_castsensoridle', 'mode_aggressive', 'mode_canmove'),
'27' => array('mode_randomtarget', 'mode_canattack', 'mode_aggressive'),
)
?>
================================================
FILE: config/monstermode.php
================================================
'Aggressive',
'mode_angry' => 'Angry',
'mode_assist' => 'Assist',
'mode_canattack' => 'Can Attack',
'mode_canmove' => 'Can Move',
'mode_castsensorchase' => 'Cast Sensor Chase',
'mode_castsensoridle' => 'Cast Sensor Idle',
'mode_changechase' => 'Change Chase',
'mode_changetargetchase' => 'Change Target Chase',
'mode_changetargetmelee' => 'Change Target Melee',
'mode_detector' => 'Detector',
'mode_fixeditemdrop' => 'Fixed Item Drop',
'mode_ignoremagic' => 'Ignore Magic',
'mode_ignoremelee' => 'Ignore Melee',
'mode_ignoremisc' => 'Ignore Misc',
'mode_ignoreranged' => 'Ignore Ranged',
'mode_knockbackimmune' => 'Knockback Immune',
'mode_looter' => 'Looter',
'mode_mvp' => 'MVP',
'mode_norandomwalk' => 'Plant',
'mode_randomtarget' => 'Random Target',
'mode_skillimmune' => 'Skill Immune',
'mode_statusimmune' => 'Status Immune',
'mode_targetweak' => 'Target Weak',
'mode_teleportblock' => 'Teleport Block',
)
?>
================================================
FILE: config/picktypes.php
================================================
'Admin',
'B' => 'Buy Store',
'C' => 'Consumed',
'D' => 'Stolen/Ganked',
'E' => 'Mailed',
'F' => 'Bound Retrieval',
'G' => 'Guild Storage',
//'H' => '',
'I' => 'Auctioned',
//'J' => '',
'K' => 'Bank',
'L' => 'Looted',
'M' => 'Monster',
'N' => 'NPC (Script)',
'O' => 'Produced',
'P' => 'Player',
'Q' => 'Quest',
'R' => 'Storage',
'S' => 'NPC (Shop)',
'T' => 'Traded',
'U' => 'MVP',
'V' => 'Vended',
//'W' => '',
'X' => 'Other',
'Y' => 'Lottery',
'Z' => 'Merged',
'$' => 'Cash',
);
?>
================================================
FILE: config/races.php
================================================
'Formless',
'Undead' => 'Undead',
'Brute' => 'Brute',
'Plant' => 'Plant',
'Insect' => 'Insect',
'Fish' => 'Fish',
'Demon' => 'Demon',
'Demihuman' => 'Demi-Human',
'Angel' => 'Angel',
'Dragon' => 'Dragon'
)
?>
================================================
FILE: config/servers.php
================================================
'FluxRO',
// Global database configuration (excludes logs database configuration).
'DbConfig' => array(
//'Socket' => '/tmp/mysql.sock',
//'Port' => 3306,
//'Encoding' => 'utf8', // Connection encoding -- use whatever here your MySQL tables collation is.
'Convert' => 'utf8',
// -- 'Convert' option only works when 'Encoding' option is specified and iconv (http://php.net/iconv) is available.
// -- It specifies the encoding to convert your MySQL data to on the website (most likely needs to be utf8)
'Hostname' => '127.0.0.1',
'Username' => 'ragnarok',
'Password' => 'ragnarok',
'Database' => 'ragnarok',
'Persistent' => true,
'Timezone' => null // Example: '+0:00' is UTC.
// The possible values of 'Timezone' is as documented from the MySQL website:
// "The value can be given as a string indicating an offset from UTC, such as '+10:00' or '-6:00'."
// "The value can be given as a named time zone, such as 'Europe/Helsinki', 'US/Eastern', or 'MET'." (see below continuation!)
// **"Named time zones can be used only if the time zone information tables in the mysql database have been created and populated."
),
// This is kept separate because many people choose to have their logs
// database accessible under different credentials, and often on a
// different server entirely to ensure the reliability of the log data.
'LogsDbConfig' => array(
//'Socket' => '/tmp/mysql.sock',
//'Port' => 3306,
//'Encoding' => null, // Connection encoding -- use whatever here your MySQL tables collation is.
'Convert' => 'utf8',
// -- 'Convert' option only works when 'Encoding' option is specified and iconv (http://php.net/iconv) is available.
// -- It specifies the encoding to convert your MySQL data to on the website (most likely needs to be utf8)
'Hostname' => '127.0.0.1',
'Username' => 'ragnarok',
'Password' => 'ragnarok',
'Database' => 'ragnarok',
'Persistent' => true,
'Timezone' => null // Possible values is as described in the comment in DbConfig.
),
// Web server configuration.
'WebDbConfig' => array(
'Hostname' => '127.0.0.1',
'Username' => 'ragnarok',
'Password' => 'ragnarok',
'Database' => 'ragnarok',
'Persistent' => true
),
// Login server configuration.
'LoginServer' => array(
'Address' => '127.0.0.1',
'Port' => 6900,
'UseMD5' => false,
'NoCase' => true, // rA account case-sensitivity; Default: Case-INsensitive (true).
'GroupID' => 0, // Default account group ID during registration.
//'Database' => 'ragnarok'
),
'CharMapServers' => array(
array(
'ServerName' => 'FluxRO',
'Renewal' => true,
'MaxCharSlots' => 9,
'DateTimezone' => null, // Specifies game server's timezone for this char/map pair. (See: http://php.net/timezones)
//'ResetDenyMaps' => 'sec_pri', // Defaults to 'sec_pri'. This value can be an array of map names.
//'Database' => 'ragnarok', // Defaults to DbConfig.Database
'ExpRates' => array(
'Base' => 100, // Rate at which (base) exp is given
'Job' => 100, // Rate at which job exp is given
'Mvp' => 100 // MVP bonus exp rate
),
'DropRates' => array(
// If drop rate was below this amount and bonus is applied to it, the bonus can't make it exceed this amount.
'DropRateCap' => 9000,
// The rate the common items (in the ETC tab, besides card) are dropped
'Common' => 100,
'CommonBoss' => 100,
'CommonMVP' => 100,
'CommonMin' => 1,
'CommonMax' => 10000,
// The rate healing items (that restore HP or SP) are dropped
'Heal' => 100,
'HealBoss' => 100,
'HealMVP' => 100,
'HealMin' => 1,
'HealMax' => 10000,
// The rate usable items (in the item tab other then healing items) are dropped
'Useable' => 100,
'UseableBoss' => 100,
'UseableMVP' => 100,
'UseableMin' => 1,
'UseableMax' => 10000,
// The rate at which equipment is dropped
'Equip' => 100,
'EquipBoss' => 100,
'EquipMVP' => 100,
'EquipMin' => 1,
'EquipMax' => 10000,
// The rate at which cards are dropped
'Card' => 100,
'CardBoss' => 100,
'CardMVP' => 100,
'CardMin' => 1,
'CardMax' => 10000,
// The rate adjustment for the MVP items that the MVP gets directly in their inventory
'MvpItem' => 100,
'MvpItemMin' => 1,
'MvpItemMax' => 10000,
// 0 - official order (Show message "Note: Only one MVP drop will be rewarded.") , 2 - all items
'MvpItemMode' => 0,
),
'CharServer' => array(
'Address' => '127.0.0.1',
'Port' => 6121
),
'MapServer' => array(
'Address' => '127.0.0.1',
'Port' => 5121
),
// -- WoE days and times --
// First parameter: Starding day 0=Sunday / 1=Monday / 2=Tuesday / 3=Wednesday / 4=Thursday / 5=Friday / 6=Saturday
// Second parameter: Starting hour in 24-hr format.
// Third paramter: Ending day (possible value is same as starting day).
// Fourth (final) parameter: Ending hour in 24-hr format.
// ** (Note, invalid times are ignored silently.)
'WoeDayTimes' => array(
//array(0, '12:00', 0, '14:00'), // Example: Starts Sunday 12:00 PM and ends Sunday 2:00 PM
//array(3, '14:00', 3, '15:00') // Example: Starts Wednesday 2:00 PM and ends Wednesday 3:00 PM
),
// Modules and/or actions to disallow access to during WoE.
'WoeDisallow' => array(
array('module' => 'character', 'action' => 'online'), // Disallow access to "Who's Online" page during WoE.
array('module' => 'character', 'action' => 'mapstats') // Disallow access to "Map Statistics" page during WoE.
)
)
)
)
);
?>
================================================
FILE: config/shopcategories.php
================================================
'Headgears', // Headgears category, most likely doesn't include wings.
1 => 'Wings', // Wings category, though most of the time wings are headgear, this is more dedicated.
2 => 'Armors', // Armors category, most likely doesn't include headgears.
3 => 'Weapons', // Weapons category.
4 => 'Healing Items', // Good category to place healing items under, e.g., Yggdrasil Berries and the like.
5 => 'Pets', // Some people may choose to sell pet eggs or pet-related items under here.
6 => 'Miscellaneous', // Anything you can't really categorize, you can put under this category.
7 => 'Cards' // Cards category.
);
?>
================================================
FILE: config/sizes.php
================================================
'Small',
'Medium' => 'Medium',
'Large' => 'Large'
)
?>
================================================
FILE: config/trade_restrictions.php
================================================
'Can\'t be dropped',
'trade_notrade' => 'Can\'t be traded with player',
'trade_tradepartner' => 'Can\'t be traded with partner',
'trade_nosell' => 'Can\'t be sold to NPC',
'trade_nocart' => 'Can\'t be put in Cart',
'trade_nostorage' => 'Can\'t be put in Storage',
'trade_noguildstorage' => 'Can\'t be put in Guild Storage',
'trade_nomail' => 'Can\'t be attached in Mail',
'trade_noauction' => 'Can\'t be auctioned'
)
?>
================================================
FILE: data/captcha/fonts/index.html
================================================
================================================
FILE: data/captcha/index.html
================================================
================================================
FILE: data/emblem/index.html
================================================
================================================
FILE: data/index.html
================================================
================================================
FILE: data/items/icons/index.html
================================================
================================================
FILE: data/items/images/index.html
================================================
================================================
FILE: data/items/index.html
================================================
================================================
FILE: data/itemshop/index.html
================================================
================================================
FILE: data/jobs/images/F/index.html
================================================
================================================
FILE: data/jobs/images/M/index.html
================================================
================================================
FILE: data/jobs/images/index.html
================================================
================================================
FILE: data/jobs/index.html
================================================
================================================
FILE: data/logs/index.html
================================================
================================================
FILE: data/monsters/index.html
================================================
================================================
FILE: data/npc/DonationNPC.txt
================================================
map,x,y,d script Donor Rewards Redeemer 987,{
// ----------------- NPC Settings -----------------
// --- SET THESE BEFORE LOADING THE SCRIPT! ---
// Server Name
set .serverName$,"FluxRO";
// NPC Name to display during chat.
// Default: "[Donor Rewards Redeemer]"
set .npcName$,"[Donor Rewards Redeemer]";
// DO NOT CHANGE THIS!
// Default: "cp_redeemlog"
set .redeemTable$,"cp_redeemlog";
// Display Credits to FluxCP Creators?
// Help promote our product if its useful.
// 0 = Disable. 1 = Enable.
// Default: 1
set .showCredits,1;
// Max number of unique items to redeem at a time.
// DO NOT RAISE THIS VALUE ABOVE 128 WITHOUT
// MAKING THE NECESSARY SCRIPT ENGINE MODS
// FOR SCRIPT ARRAY SIZING! DANGEROUS!
// Default: 128
set .numRedemptionsSimul,128;
// --------------- End NPC Settings ---------------
// ----------------- Begin Script -----------------
mes .npcName$;
mes "Well hello there " + (Sex ? "good sir!" : "young madam!");
mes "How may I be of assistance to you on this fine day?";
next;
prompt("I wish to redeem items:Who might you be?:I am merely perusing the area");
mes .npcName$;
switch(@menu) {
case 1:
query_sql "SELECT `id`, `nameid`, `quantity` FROM `" + escape_sql(.redeemTable$) + "` WHERE `account_id` = " + getcharid(3) + " AND `redeemed` = 0 LIMIT " + .numRedemptionsSimul,.@id,.@nameid,.@quantity;
if (getarraysize(.@id) > 0) {
mes "Items Pending Redemption: " + getarraysize(.@id);
for (set .@i,0; .@i < getarraysize(.@id); set .@i,.@i+1)
if (checkweight(.@nameid[.@i],.@quantity[.@i]) == 0) {
mes "I'm terribly sorry, but you are carrying too much to accept " + (.@i ? "any more of " : " ") + "your rewards at this time.";
mes "Please come back with fewer items.";
} else {
query_sql "UPDATE `" + escape_sql(.redeemTable$) + "` SET `char_id` = " + getcharid(0) + ", `redeemed` = 1, `redemption_date` = NOW() WHERE `id` = " + .@id[.@i];
getitem .@nameid[.@i],.@quantity[.@i];
mes .@quantity[.@i] + "x " + getitemname(.@nameid[.@i]);
}
if (.@i == getarraysize(.@id)) {
mes "Thank you for your patronage " + (Sex ? "fine sir." : "ma'am.");
mes "Please enjoy your stay on " + .serverName$ + "!";
}
if (.showCredits)
callfunc "F_FluxCredits";
} else {
mes "My records indicate that there are no rewards awaiting to be redeemed.";
mes "My deepest apologies for the misunderstanding.";
}
break;
case 2:
mes "I am here to allow for the redemption of rewards for donations to " + .serverName$ + ".";
mes "Donations may be made to the server via the control panel.";
break;
default:
mes "Very well then.";
mes "Good day to you.";
break;
}
close;
// ------------------ End Script ------------------
}
// ------------ Credits to FluxCP Creators ------------
// - Please do not modify or delete this function or -
// - its contents. To disable the credits from being -
// - shown, set .showCredits to 0 in the NPC Settings -
// - at the top of this file. -
// ----------------------------------------------------
function script F_FluxCredits {
mes "-----------------------------------";
mes "Powered by Flux Control Panel.";
mes "Copyright � 2008-2012 Matthew Harris and Nikunj Mehta.";
mes "http://fluxcp.googlecode.com/";
return;
}
================================================
FILE: data/npc/PeakNPC.txt
================================================
- script Highest Peak -1,{
OnPCLoginEvent:
// Assign current number of online players
sleep 1000;
.onlineusers = getusers(1);
// Today's date
.date$ = gettime(DT_YEAR)+"-"+gettime(DT_MONTH)+"-"+gettime(DT_DAYOFMONTH);
// Query for the highest peak in the database
query_sql("SELECT `users` FROM `" + .sqltable$ + "` ORDER BY `users` LIMIT 1",.@countusers);
if(getarraysize(.@countusers) == 0) {
// There doesn't seem to be a row in our table, so lets create one
query_sql("INSERT INTO `" + .sqltable$ + "` (`users`, `date`) VALUES (" + .onlineusers + ", '" + .date$ + "')");
} else {
if(.onlineusers > .@countusers[0]) {
// This is where we check if we want to announce a new peak
if(.displaypeakannounce == 1){
announce "We have reached a new player peak! We now have "+.onlineusers+" online!",bc_all;
}
// Now lets update the table with our new player peak
query_sql("UPDATE `" + .sqltable$ + "` SET `users` = '" + .onlineusers + "', `date` = '" + .date$ + "'");
}
}
end;
OnInit:
// CONFIGS
// We need an SQL table name first
.sqltable$ = "cp_onlinepeak";
// Would you like an announcement when you reach a higher peak?
// 1 = Yes
// 0 = No
.displaypeakannounce = 1;
// END CONFIGS
}
================================================
FILE: data/npc/index.html
================================================
================================================
FILE: data/npc/support_cmd.txt
================================================
//===== rAthena Script =======================================
//= @support
//===== By: ==================================================
//= Akkarin
//===== Current Version: =====================================
//= 1.00.01
//===== Description: =========================================
//= Script control over @support
//============================================================
- script atcmd_support -1,{
OnInit:
bindatcmd("support","atcmd_support::Onsupport");
end;
Onsupport:
query_sql "SELECT `cat_id`, `name` FROM `cp_servicedeskcat` WHERE `display` = 1 ORDER BY `name`", @cat_id, @cat_name$;
mes "Please select one of the following categories";
set @j,0;
for(set @i, 0; @i < getarraysize(@cat_id); set @i, @i + 1){ set @menulist$[@j],@cat_name$[@i]; set @menureference[@j],@i; set @j,@j+1; }
menu @menulist$[0],-,@menulist$[1],-,@menulist$[2],-,@menulist$[3],-,@menulist$[4],-,@menulist$[5],-,@menulist$[6],-,@menulist$[7],-,@menulist$[8],-,@menulist$[9],-,@menulist$[10],-,@menulist$[11],-,@menulist$[12],-,@menulist$[13],-,@menulist$[14],-,@menulist$[15],-,@menulist$[16],-,@menulist$[17],-,@menulist$[18],-,@menulist$[19],-,@menulist$[20],-,@menulist$[21],-,@menulist$[22],-,@menulist$[23],-,@menulist$[24],-,@menulist$[25],-,@menulist$[26],-,@menulist$[27],-,@menulist$[28],-,@menulist$[29],-,@menulist$[30],-;
next;
mes "Next, please type in a subject line";
input @subject$;
next;
mes "Please tell us what the problem is. You must make sure that you keep this brief so all the text fits into the box!";
input @body$;
next;
mes "This is what we have so far.", "Click 'Next' to view each entry, then submit your ticket.";
next;
mes "^FF0000Category^000000", "You selected "+@cat_name$[@menureference[@menu-1]]; next;
mes "^FF0000Subject^000000", @subject$; next;
mes "^FF0000Body^000000", @body$; next;
mes "If this is all correct, your ticket will be created.", "Continue?";
if(select("Yes:No")==1) {
query_sql "SELECT `email`, `last_ip` FROM `login` WHERE `account_id` = '"+getcharid(3)+"'", @player_email$, @player_lastip$;
query_sql("INSERT INTO `cp_servicedesk` (`account_id`, `category`, `char_id`, `timestamp`, `sslink`, `chatlink`, `videolink`, `subject`, `text`, `ip`, `curemail`) VALUES ('"+getcharid(3)+"', '"+@cat_id[@menureference[@menu-1]]+"', '"+getcharid(0)+"', NOW(), '0', '0', '0', '"+@subject$+"', '"+@body$+"', '"+@player_lastip$+"', '"+@player_email$+"')");
mes "Ticket created.";
close;
} else {
mes "Submission terminated.";
close;
end;
}
end;
}
================================================
FILE: data/npc/web_commands.txt
================================================
//===== rAthena Script =======================================
//= Web Commander
//===== By: ==================================================
//= Akkarin, rAthena FluxCP
//===== Description: =========================================
//= Simple script to check sql table for commands to process.
//= Data is input via FluxCP module and checked for unexecuted
//= commands every 2 seconds.
//===== Additional Comments: =================================
//= 1.0 First Version.
//= 1.1 Fixed code to prevent single-run senarios.
//============================================================
- script Commands -1,{
OnInit:
start:
initnpctimer;
end;
OnTimer2000:
.@nb2 = query_sql("SELECT COUNT(*) FROM cp_commands WHERE done=0",.@count);
if(.@count != 0) {
.@nb = query_sql("SELECT command,id,account_id FROM cp_commands WHERE done=0 ORDER BY id LIMIT 1",.@command$,.@id,.@account_id);
if(compare(.@command$,"@")) {
attachrid(.@account_id[0]);
atcommand .@command$[0];
} else charcommand .@command$[0];
query_sql("UPDATE cp_commands SET done='1' WHERE command='"+.@command$[0]+"' AND id='"+.@id[0]+"'");
.@nb = 0; .@nb2 = 0; .@count = 0; .@command$ = 0; .@id = 0; .@account_id = 0;
}
stopnpctimer;
goto start;
}
================================================
FILE: data/paypal/button.php
================================================
$session->loginAthenaGroup->serverName, 'account_id' => $session->account->account_id);
$customDataEscaped = htmlspecialchars(base64_encode(serialize($customDataArray)));
$businessEmail = htmlspecialchars(Flux::config('PayPalBusinessEmail'));
$donationCurrency = htmlspecialchars(Flux::config('DonationCurrency'));
$creditExchangeRate = Flux::config('CreditExchangeRate');
$donationCredits = floor($amount / $creditExchangeRate);
$itemName = htmlspecialchars(sprintf('Donation Credits: %s CREDIT(s)', number_format($donationCredits)));
?>
================================================
FILE: data/paypal/index.html
================================================
================================================
FILE: data/schemas/charmapdb/cp_charprefs.20080929191525.sql
================================================
CREATE TABLE IF NOT EXISTS `cp_charprefs` (
`id` int(10) unsigned NOT NULL auto_increment,
`account_id` int(11) unsigned NOT NULL,
`char_id` int(11) unsigned NOT NULL,
`name` varchar(80) NOT NULL,
`value` varchar(255) default NULL,
`create_date` datetime default NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM COMMENT='Character preferences.';
================================================
FILE: data/schemas/charmapdb/cp_charprefs.20081109093448.sql
================================================
ALTER TABLE `cp_charprefs` ADD INDEX ( `account_id` , `char_id` ) ;
================================================
FILE: data/schemas/charmapdb/cp_charprefs.20120816150540.sql
================================================
ALTER TABLE `cp_charprefs` ADD INDEX (`char_id`);
================================================
FILE: data/schemas/charmapdb/cp_commands.20160608065501.sql
================================================
CREATE TABLE IF NOT EXISTS `cp_commands` (
`id` int(6) NOT NULL AUTO_INCREMENT,
`command` varchar(128) NOT NULL DEFAULT '0',
`issuer` varchar(32) NOT NULL DEFAULT '0',
`account_id` int(12) NOT NULL DEFAULT '0',
`done` int(1) NOT NULL DEFAULT '0',
`timestamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ;
================================================
FILE: data/schemas/charmapdb/cp_itemdesc.20170210033400.sql
================================================
CREATE TABLE IF NOT EXISTS `cp_itemdesc` (
`itemid` int(10) unsigned NOT NULL auto_increment,
`itemdesc` text NOT NULL,
PRIMARY KEY (`itemid`)
) ENGINE=MyISAM COMMENT='Stored item descriptions from parsed itemInfo.';
================================================
FILE: data/schemas/charmapdb/cp_itemshop.20080928225124.sql
================================================
CREATE TABLE IF NOT EXISTS `cp_itemshop` (
`id` int(11) unsigned NOT NULL auto_increment,
`nameid` int(11) unsigned NOT NULL default '0',
`quantity` int(11) unsigned NOT NULL default '0',
`cost` int(11) unsigned NOT NULL,
`info` text,
`create_date` datetime NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM COMMENT='Item shop';
================================================
FILE: data/schemas/charmapdb/cp_itemshop.20081109093448.sql
================================================
DROP PROCEDURE IF EXISTS cp_itemshop_20081109093448;
CREATE PROCEDURE cp_itemshop_20081109093448() BEGIN
DECLARE CONTINUE HANDLER FOR 1060 BEGIN END;
ALTER TABLE `cp_itemshop` ADD `use_existing` TINYINT NOT NULL DEFAULT '0' AFTER `info`;
END;
CALL cp_itemshop_20081109093448();
DROP PROCEDURE cp_itemshop_20081109093448;
================================================
FILE: data/schemas/charmapdb/cp_itemshop.20081128093449.sql
================================================
ALTER TABLE `cp_itemshop` ADD INDEX ( `nameid` ) ;
================================================
FILE: data/schemas/charmapdb/cp_itemshop.20090104190020.sql
================================================
DROP PROCEDURE IF EXISTS cp_itemshop_20090104190020;
CREATE PROCEDURE cp_itemshop_20090104190020() BEGIN
DECLARE CONTINUE HANDLER FOR 1060 BEGIN END;
ALTER TABLE `cp_itemshop` ADD `category` INT(11) NULL AFTER `nameid`;
END;
CALL cp_itemshop_20090104190020();
DROP PROCEDURE cp_itemshop_20090104190020;
================================================
FILE: data/schemas/charmapdb/cp_onlinepeak.20131120120201.sql
================================================
CREATE TABLE IF NOT EXISTS `cp_onlinepeak` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`users` int(10) unsigned NOT NULL DEFAULT '0',
`date` date NOT NULL,
PRIMARY KEY (`id`)
) ENGINE = MYISAM;
================================================
FILE: data/schemas/charmapdb/cp_redeemlog.20080928225124.sql
================================================
CREATE TABLE IF NOT EXISTS `cp_redeemlog` (
`id` int(11) unsigned NOT NULL auto_increment,
`nameid` int(11) unsigned NOT NULL default '0',
`quantity` int(11) unsigned NOT NULL default '0',
`cost` int(11) unsigned NOT NULL,
`account_id` int(11) unsigned NOT NULL,
`char_id` int(11) unsigned default NULL,
`redeemed` tinyint(1) unsigned NOT NULL,
`redemption_date` datetime default NULL,
`purchase_date` datetime NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM COMMENT='Log of redeemed donation items.';
================================================
FILE: data/schemas/charmapdb/cp_redeemlog.20081001054354.sql
================================================
DROP PROCEDURE IF EXISTS cp_redeemlog_20081001054354;
CREATE PROCEDURE cp_redeemlog_20081001054354() BEGIN
DECLARE CONTINUE HANDLER FOR 1060 BEGIN END;
ALTER TABLE `cp_redeemlog`
ADD `credits_before` INT( 10 ) NOT NULL ,
ADD `credits_after` INT( 10 ) NOT NULL;END;
CALL cp_redeemlog_20081001054354();
DROP PROCEDURE cp_redeemlog_20081001054354;
================================================
FILE: data/schemas/charmapdb/cp_redeemlog.20081109093448.sql
================================================
ALTER TABLE `cp_redeemlog` ADD INDEX ( `nameid` , `account_id` , `char_id` ) ;
================================================
FILE: data/schemas/charmapdb/cp_xferlog.20080928225124.sql
================================================
CREATE TABLE IF NOT EXISTS `cp_xferlog` (
`id` int(10) unsigned NOT NULL auto_increment,
`from_account_id` int(10) unsigned NOT NULL,
`target_account_id` int(10) unsigned NOT NULL,
`target_char_id` int(11) unsigned NOT NULL,
`amount` int(10) unsigned NOT NULL,
`for_free` tinyint(1) unsigned NOT NULL default '0',
`transfer_date` datetime NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM COMMENT='Credit transfer log.';
================================================
FILE: data/schemas/charmapdb/cp_xferlog.20081109093448.sql
================================================
ALTER TABLE `cp_xferlog` ADD INDEX ( `from_account_id` , `target_account_id` , `target_char_id` ) ;
================================================
FILE: data/schemas/charmapdb/index.html
================================================
================================================
FILE: data/schemas/index.html
================================================
================================================
FILE: data/schemas/logindb/cp_banlog.20131213174201.sql
================================================
CREATE TABLE IF NOT EXISTS `cp_banlog` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`account_id` int(11) unsigned NOT NULL,
`banned_by` int(11) unsigned DEFAULT NULL,
`ban_type` tinyint(1) NOT NULL,
`ban_until` datetime NOT NULL,
`ban_date` datetime NOT NULL,
`ban_reason` text NOT NULL,
PRIMARY KEY (`id`),
KEY `account_id` (`account_id`,`banned_by`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ;
================================================
FILE: data/schemas/logindb/cp_cmsnews.20131120145701.sql
================================================
CREATE TABLE IF NOT EXISTS `cp_cmsnews` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`title` varchar(100) NOT NULL,
`body` text NOT NULL,
`link` varchar(100) NOT NULL,
`author` varchar(100) NOT NULL,
`created` datetime NOT NULL,
`modified` datetime NOT NULL,
PRIMARY KEY (`id`)
) ENGINE = MYISAM;
================================================
FILE: data/schemas/logindb/cp_cmspages.20131120161901.sql
================================================
CREATE TABLE IF NOT EXISTS `cp_cmspages` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`path` varchar(100) NOT NULL,
`title` varchar(100) NOT NULL,
`body` text NOT NULL,
`modified` datetime NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=2 ;
INSERT INTO `cp_cmspages` (`id`, `path`, `title`, `body`, `modified`) VALUES
(1, 'rules', 'Rules', 'This is a rules page.', '2013-11-20 00:00:00'),
(2, 'downloads', 'Downloads', 'This is a download page.', '2017-07-20 00:00:00');
================================================
FILE: data/schemas/logindb/cp_cmssettings.20131120145801.sql
================================================
CREATE TABLE IF NOT EXISTS `cp_cmssettings` (
`name` varchar(128) NOT NULL,
`value` varchar(128) NOT NULL,
UNIQUE KEY `name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
================================================
FILE: data/schemas/logindb/cp_createlog.20131213174201.sql
================================================
CREATE TABLE IF NOT EXISTS `cp_createlog` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`account_id` int(11) unsigned NOT NULL,
`userid` varchar(23) NOT NULL,
`user_pass` varchar(32) NOT NULL,
`sex` enum('M','F','S') NOT NULL DEFAULT 'M',
`email` varchar(39) NOT NULL,
`reg_date` datetime NOT NULL,
`reg_ip` varchar(100) NOT NULL,
`delete_date` datetime DEFAULT NULL,
`confirmed` tinyint(1) NOT NULL DEFAULT '1',
`confirm_code` varchar(32) DEFAULT NULL,
`confirm_expire` datetime DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `name` (`userid`),
KEY `account_id` (`account_id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ;
================================================
FILE: data/schemas/logindb/cp_createlog.20170720151901.sql
================================================
ALTER TABLE `cp_createlog` CHANGE `reg_date` `reg_date` DATETIME NOT NULL;
================================================
FILE: data/schemas/logindb/cp_createlog.20250614124331.sql
================================================
ALTER TABLE cp_createlog MODIFY COLUMN reg_ip VARCHAR(39) NOT NULL;
================================================
FILE: data/schemas/logindb/cp_credits.20131213174201.sql
================================================
CREATE TABLE IF NOT EXISTS `cp_credits` (
`account_id` int(11) unsigned NOT NULL,
`balance` int(11) unsigned NOT NULL DEFAULT '0',
`last_donation_date` datetime DEFAULT NULL,
`last_donation_amount` float unsigned DEFAULT NULL,
PRIMARY KEY (`account_id`),
KEY `account_id` (`account_id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 COMMENT='Donation credits balance for a given account.';
================================================
FILE: data/schemas/logindb/cp_emailchange.20131213174201.sql
================================================
CREATE TABLE IF NOT EXISTS `cp_emailchange` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`code` varchar(32) NOT NULL,
`account_id` int(10) NOT NULL,
`old_email` varchar(39) NOT NULL,
`new_email` varchar(39) NOT NULL,
`request_date` datetime NOT NULL,
`request_ip` varchar(15) NOT NULL,
`change_date` datetime DEFAULT NULL,
`change_ip` varchar(15) DEFAULT NULL,
`change_done` tinyint(4) NOT NULL DEFAULT '0',
PRIMARY KEY (`id`),
KEY `account_id` (`account_id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ;
================================================
FILE: data/schemas/logindb/cp_emailchange.20250609133400.sql
================================================
ALTER TABLE cp_emailchange
MODIFY COLUMN request_ip VARCHAR(39) NOT NULL,
MODIFY COLUMN change_ip VARCHAR(39) DEFAULT NULL;
================================================
FILE: data/schemas/logindb/cp_ipbanlog.20120816150540.sql
================================================
CREATE TABLE IF NOT EXISTS `cp_ipbanlog` (
`id` int(10) unsigned NOT NULL auto_increment,
`ip_address` varchar(15) NOT NULL,
`banned_by` int(11) unsigned default NULL,
`ban_type` tinyint(1) NOT NULL,
`ban_until` datetime NOT NULL,
`ban_date` datetime NOT NULL,
`ban_reason` text NOT NULL,
PRIMARY KEY (`id`),
INDEX (`ip_address`),
INDEX (`banned_by`)
) ENGINE=MyISAM ;
================================================
FILE: data/schemas/logindb/cp_ipbanlog.20250609133400.sql
================================================
ALTER TABLE cp_ipbanlog MODIFY COLUMN ip_address VARCHAR(39) NOT NULL;
================================================
FILE: data/schemas/logindb/cp_loginlog.20131213174201.sql
================================================
CREATE TABLE IF NOT EXISTS `cp_loginlog` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`account_id` int(10) DEFAULT NULL,
`username` varchar(23) NOT NULL,
`password` varchar(32) NOT NULL,
`ip` varchar(15) NOT NULL,
`login_date` datetime NOT NULL,
`error_code` tinyint(1) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `account_id` (`account_id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ;
================================================
FILE: data/schemas/logindb/cp_loginlog.20250609133400.sql
================================================
ALTER TABLE cp_loginlog MODIFY COLUMN ip VARCHAR(39) NOT NULL;
================================================
FILE: data/schemas/logindb/cp_loginprefs.20131213174201.sql
================================================
CREATE TABLE IF NOT EXISTS `cp_loginprefs` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`account_id` int(11) unsigned NOT NULL,
`name` varchar(80) NOT NULL,
`value` varchar(255) DEFAULT NULL,
`create_date` datetime DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `account_id` (`account_id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 COMMENT='Account preferences' AUTO_INCREMENT=1 ;
================================================
FILE: data/schemas/logindb/cp_pwchange.20131213174201.sql
================================================
CREATE TABLE IF NOT EXISTS `cp_pwchange` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`account_id` int(10) NOT NULL,
`old_password` varchar(32) NOT NULL,
`new_password` varchar(32) DEFAULT NULL,
`change_date` datetime NOT NULL,
`change_ip` varchar(15) NOT NULL,
PRIMARY KEY (`id`),
KEY `account_id` (`account_id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ;
================================================
FILE: data/schemas/logindb/cp_pwchange.20250609133400.sql
================================================
ALTER TABLE cp_pwchange MODIFY COLUMN change_ip VARCHAR(39) NOT NULL;
================================================
FILE: data/schemas/logindb/cp_resetpass.20131213174201.sql
================================================
CREATE TABLE IF NOT EXISTS `cp_resetpass` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`code` varchar(32) NOT NULL,
`account_id` int(10) NOT NULL,
`old_password` varchar(32) NOT NULL,
`new_password` varchar(32) DEFAULT NULL,
`request_date` datetime NOT NULL,
`request_ip` varchar(15) NOT NULL,
`reset_date` datetime DEFAULT NULL,
`reset_ip` varchar(15) DEFAULT NULL,
`reset_done` tinyint(4) NOT NULL DEFAULT '0',
PRIMARY KEY (`id`),
KEY `account_id` (`account_id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ;
================================================
FILE: data/schemas/logindb/cp_resetpass.20250609133400.sql
================================================
ALTER TABLE cp_resetpass
MODIFY COLUMN request_ip VARCHAR(39) NOT NULL,
MODIFY COLUMN reset_ip VARCHAR(39) DEFAULT NULL;
================================================
FILE: data/schemas/logindb/cp_servicedesk.20131122010001.sql
================================================
CREATE TABLE IF NOT EXISTS `cp_servicedesk` (
`ticket_id` int(6) NOT NULL AUTO_INCREMENT,
`account_id` int(7) NOT NULL,
`category` int(6) NOT NULL,
`status` varchar(12) NOT NULL DEFAULT 'Pending',
`char_id` text NOT NULL,
`timestamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`sslink` text NOT NULL,
`chatlink` text NOT NULL,
`videolink` text NOT NULL,
`subject` varchar(64) NOT NULL DEFAULT '0',
`text` text NOT NULL,
`ip` varchar(15) NOT NULL DEFAULT '0',
`team` int(1) NOT NULL DEFAULT '1',
`curemail` text NOT NULL,
`lastreply` varchar(24) NOT NULL DEFAULT '0',
PRIMARY KEY (`ticket_id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=1000 ;
================================================
FILE: data/schemas/logindb/cp_servicedesk.20250609133400.sql
================================================
ALTER TABLE cp_servicedesk MODIFY COLUMN ip VARCHAR(39) NOT NULL DEFAULT '0';
================================================
FILE: data/schemas/logindb/cp_servicedeska.20131122010001.sql
================================================
CREATE TABLE IF NOT EXISTS `cp_servicedeska` (
`action_id` int(6) NOT NULL AUTO_INCREMENT,
`ticket_id` int(6) NOT NULL,
`author` varchar(32) NOT NULL,
`text` text NOT NULL,
`action` text NOT NULL,
`timestamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`ip` varchar(15) NOT NULL DEFAULT '0',
`isstaff` int(1) NOT NULL DEFAULT '0',
PRIMARY KEY (`action_id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ;
================================================
FILE: data/schemas/logindb/cp_servicedeska.20250609133400.sql
================================================
ALTER TABLE cp_servicedeska MODIFY COLUMN ip VARCHAR(39) NOT NULL DEFAULT '0';
================================================
FILE: data/schemas/logindb/cp_servicedeskcat.20131122010001.sql
================================================
CREATE TABLE IF NOT EXISTS `cp_servicedeskcat` (
`cat_id` int(3) NOT NULL AUTO_INCREMENT,
`name` varchar(32) NOT NULL,
`display` int(1) NOT NULL DEFAULT '1',
PRIMARY KEY (`cat_id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=4 ;
INSERT INTO `cp_servicedeskcat` (`cat_id`, `name`, `display`) VALUES
(1, 'Technical Support', 1),
(2, 'General Support', 1),
(3, 'Report an Abuse', 1);
================================================
FILE: data/schemas/logindb/cp_servicedesksettings.20131122010001.sql
================================================
CREATE TABLE IF NOT EXISTS `cp_servicedesksettings` (
`account_id` int(7) NOT NULL,
`account_name` varchar(32) NOT NULL,
`prefered_name` varchar(32) NOT NULL,
`team` int(1) NOT NULL,
`emailalerts` int(1) NOT NULL DEFAULT '0',
`timestamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`account_id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
================================================
FILE: data/schemas/logindb/cp_trusted.20131213174201.sql
================================================
CREATE TABLE IF NOT EXISTS `cp_trusted` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`account_id` int(11) unsigned NOT NULL,
`email` varchar(255) NOT NULL,
`create_date` datetime NOT NULL,
`delete_date` datetime DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `account_id` (`account_id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ;
================================================
FILE: data/schemas/logindb/cp_txnlog.20131213174201.sql
================================================
CREATE TABLE IF NOT EXISTS `cp_txnlog` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`account_id` int(11) unsigned DEFAULT '0',
`server_name` varchar(255) DEFAULT NULL,
`credits` int(11) DEFAULT '0',
`receiver_email` varchar(60) DEFAULT NULL,
`item_name` varchar(100) DEFAULT NULL,
`item_number` varchar(10) DEFAULT NULL,
`quantity` varchar(6) DEFAULT NULL,
`payment_status` varchar(20) DEFAULT NULL,
`pending_reason` varchar(20) DEFAULT NULL,
`payment_date` varchar(40) DEFAULT NULL,
`mc_gross` varchar(20) DEFAULT NULL,
`mc_fee` varchar(20) DEFAULT NULL,
`tax` varchar(20) DEFAULT NULL,
`mc_currency` varchar(3) DEFAULT NULL,
`parent_txn_id` varchar(20) DEFAULT NULL,
`txn_id` varchar(20) DEFAULT NULL,
`txn_type` varchar(20) DEFAULT NULL,
`first_name` varchar(30) DEFAULT NULL,
`last_name` varchar(40) DEFAULT NULL,
`address_street` varchar(50) DEFAULT NULL,
`address_city` varchar(30) DEFAULT NULL,
`address_state` varchar(30) DEFAULT NULL,
`address_zip` varchar(20) DEFAULT NULL,
`address_country` varchar(30) DEFAULT NULL,
`address_status` varchar(10) DEFAULT NULL,
`payer_email` varchar(60) DEFAULT NULL,
`payer_status` varchar(10) DEFAULT NULL,
`payment_type` varchar(10) DEFAULT NULL,
`notify_version` varchar(10) DEFAULT NULL,
`verify_sign` varchar(255) DEFAULT NULL,
`referrer_id` varchar(10) DEFAULT NULL,
`process_date` datetime DEFAULT NULL,
`hold_until` datetime DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `account_id` (`account_id`),
KEY `parent_txn_id` (`parent_txn_id`),
KEY `txn_id` (`txn_id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 COMMENT='All PayPal transactions that go through the IPN handler.' AUTO_INCREMENT=1 ;
================================================
FILE: data/schemas/logindb/cp_txnlog.20170217073601.sql
================================================
ALTER TABLE `cp_txnlog` CHANGE `referrer_id` `referrer_id` VARCHAR(13) CHARACTER SET latin1 COLLATE latin1_swedish_ci NULL DEFAULT NULL;
================================================
FILE: data/schemas/logindb/cp_txnlog.20170221184601.sql
================================================
ALTER TABLE `cp_txnlog` CHANGE `address_status` `address_status` VARCHAR(11) CHARACTER SET latin1 COLLATE latin1_swedish_ci NULL DEFAULT NULL;
================================================
FILE: data/schemas/logindb/index.html
================================================
================================================
FILE: data/templates/changemail.php
================================================
You have received this e-mail because someone has filled in the "change e-mail" form after logging into your account.
If you are the one who requested this action, then please click the below link to proceed with the e-mail change.
You have received this e-mail because you or someone else has created an account
with using this
e-mail address. Point your browser to the below link to activate the account.
All unconfirmed accounts will be deleted from our system within hour(s) of registration.
We're pleased to announce that Server is back online with brand new updates and content to ...
The FluxCP Team
Email Jargen
This email was sent by FluxCP to {username} ({email}) because you have a player account with us. We will never give your details to anyone, not even a real marketing company - so we sent this email directly to you from our website!
You can unsubscribe from receiving these emails by changing your preferences on our website.
You have received this e-mail because you or someone else has filled in our "reset password" form,
requesting to reset the password of your account on our server.
You have received this e-mail because a member of the support team has replied to your ticket on the Service Desk.
Ticket ID:
{TicketID}
Updated By:
{Staff}
Note: This is an automated e-mail, please do not reply to this address.
================================================
FILE: data/tmp/index.html
================================================
================================================
FILE: data/tmp/transactions/Refunded/index.html
================================================
================================================
FILE: data/tmp/transactions/Reversed/index.html
================================================
================================================
FILE: data/tmp/transactions/web_accept/Completed/index.html
================================================
================================================
FILE: data/tmp/transactions/web_accept/Denied/index.html
================================================
================================================
FILE: data/tmp/transactions/web_accept/Pending/index.html
================================================
================================================
FILE: doc/user_lang.md
================================================
Language Files
======
How do they work?
---------
The **lang/** directory contains translations for use with FluxCP. The language used is controlled by the 'DefaultLanguage' setting in config/application.php.
Simply put, `'DefaultLanguage' => 'en_us'` will load the American English language file and use the contained strings wherever `Flux::message()` is used within theme files. There are a few others within the `lang/` directory, but unfortunately they aren't maintained.
How do we use them?
---------
For example, in a theme file that is displaying whether a players' character is male or female, you would see `` or ``.
The menus that are defined in `config/application.php` are set to automatically use the language files. For example, lets look at this specific menu:
```
'MenuItems' => array(
'MainMenuLabel' => array(
'HomeLabel' => array('module' => 'main'),
'NewsLabel' => array('module' => 'news'),
),
),
```
When the page is rendered, you will see that these strings are replaced with their counterparts from the language file.
'MainMenuLabel' becomes 'Main Menu', 'HomeLabel' becomes 'Home', 'NewsLabel' becomes 'News'.
Common Misuse
---------
Many people still think that the 'Label' portion of these strings should be removed within the config file as it's outputting 'HomeLabel' to the page instead of 'Home'. **This is incorrect.** This simply means that the theme you're using was built earlier than August 2014 and you shouldn't be using it.
================================================
FILE: doc/user_theme.md
================================================
Using a Custom Theme
======
How does it work?
---------
The Theme System in FluxCP is based on an "inheritance structure". In simple terms, this means you only need to add files to your new theme folder that you want to change.
It all works similar to the config import system in rAthena. The default theme is read first, then if there are any files matching the required view in the custom theme, then it gets loaded instead. This means that that **you don't need to copy/paste the default theme every time you create a new custom theme**.
The manifest.php file controls inheritance with `'inherit' => 'default',`.
How should my theme look?
---------
This is an example directory structure for a custom theme in a fresh install of FluxCP:
```
.
├── addons
├── config
├── data
├── doc
├── lang
├── lib
├── modules
├── themes
| ├── bootstrap
| └── cust_theme1
| └── css
| ├── flux.css
| └── customstyle.css
| └── img
| ├── bg.jpg
| └── logo.png
| └── js
| ├── flux.unitip.js
| └── ie9.js
| └── main
| ├── index.php
| └── sidebar.php
| ├── footer.php
| ├── header.php
| └── manifest.php
| ├── default
| └── installer
├── .gitignore
├── .htaccess
├── LICENSE
├── README.md
├── error.php
└── index.php
```
As you can see, there are only a few files in the **cust_theme1** folder.
How do I make it display on my website?
---------
To enable your theme, simply add it to the theme array in /config/application.php:
```'ThemeName' => array('default', 'bootstrap', 'cust_theme1'),```
If you want your new theme to always display and remove the theme selection box in the footer, remove the other themes from this array so it looks like:
```'ThemeName' => array('cust_theme1'),```
How do I know if a theme I downloaded will work?
---------
As a general rule of thumb, if your new theme has a `manifest.php` file in the theme folder, it will work with current versions of FluxCP just fine.
If it doesn't have `manifest.php`, you will need to create one. This will make the new theme able to load, but you will still have problems.
In the past, even after this theme system was introduced, theme designers have still opted to create themes reliant on old versions of FluxCP. They are lazy. Use at your own risk.
================================================
FILE: error.php
================================================
rA's Flux Control Panel: Critical Error
Critical Error
An error was encountered during the lifetime of the application.
This could be due to a variety of problems, such as a bug in the application.
However, normally it is caused by misconfiguration.
An error occurred while trying to process your request.
Please try contacting an administrator:
================================================
FILE: index.php
================================================
$value) {
if (is_string($value)) {
$arr[$key] = stripslashes($value);
}
}
}
}
set_include_path(FLUX_LIB_DIR.PATH_SEPARATOR.get_include_path());
// Default account group IDs.
require_once FLUX_CONFIG_DIR.'/groups.php';
// Some necessary Flux core libraries.
require_once 'Flux.php';
require_once 'Flux/Dispatcher.php';
require_once 'Flux/SessionData.php';
require_once 'Flux/DataObject.php';
require_once 'Flux/Authorization.php';
require_once 'Flux/Installer.php';
require_once 'Flux/PermissionError.php';
// Vendor libraries.
try {
// Initialize Flux.
Flux::initialize(array(
'appConfigFile' => FLUX_CONFIG_DIR.'/application.php',
'serversConfigFile' => FLUX_CONFIG_DIR.'/servers.php',
'appConfigFileImport' => FLUX_CONFIG_DIR.'/import/application.php',
'serversConfigFileImport' => FLUX_CONFIG_DIR.'/import/servers.php',
));
// Set time limit.
set_time_limit((int)Flux::config('ScriptTimeLimit'));
// Set default timezone for entire app.
$timezone = Flux::config('DateDefaultTimezone');
if ($timezone && !@date_default_timezone_set($timezone)) {
throw new Flux_Error("'$timezone' is not a valid timezone. Consult http://php.net/timezones for a list of valid timezones.");
}
// Create some basic directories.
$directories = array(
FLUX_DATA_DIR.'/logs/schemas',
FLUX_DATA_DIR.'/logs/schemas/logindb',
FLUX_DATA_DIR.'/logs/schemas/charmapdb',
FLUX_DATA_DIR.'/logs/transactions',
FLUX_DATA_DIR.'/logs/mail',
FLUX_DATA_DIR.'/logs/mysql',
FLUX_DATA_DIR.'/logs/mysql/errors',
FLUX_DATA_DIR.'/logs/errors',
FLUX_DATA_DIR.'/logs/errors/exceptions',
FLUX_DATA_DIR.'/logs/errors/mail',
);
// Schema log directories.
foreach (Flux::$loginAthenaGroupRegistry as $serverName => $loginAthenaGroup) {
$directories[] = FLUX_DATA_DIR."/logs/schemas/logindb/$serverName";
$directories[] = FLUX_DATA_DIR."/logs/schemas/charmapdb/$serverName";
foreach ($loginAthenaGroup->athenaServers as $athenaServer)
$directories[] = FLUX_DATA_DIR."/logs/schemas/charmapdb/$serverName/{$athenaServer->serverName}";
}
foreach ($directories as $directory) {
if (is_writable(dirname($directory)) && !is_dir($directory)) {
if (Flux::config('RequireOwnership'))
mkdir($directory, 0700);
else
mkdir($directory, 0777);
}
}
if (Flux::config('RequireOwnership') && function_exists('posix_getuid'))
$uid = posix_getuid();
$directories = array(
FLUX_DATA_DIR.'/logs' => 'log storage',
FLUX_DATA_DIR.'/itemshop' => 'item shop image',
FLUX_DATA_DIR.'/tmp' => 'temporary'
);
foreach ($directories as $directory => $directoryFunction) {
$directory = realpath($directory);
if (!is_dir($directory))
mkdir($directory, 0600);
if (!is_writable($directory) && is_dir($directory))
throw new Flux_PermissionError("The $directoryFunction directory '$directory' is not writable. Remedy with `chmod 0600 $directory`");
if (Flux::config('RequireOwnership') && function_exists('posix_getuid') && fileowner($directory) != $uid)
throw new Flux_PermissionError("The $directoryFunction directory '$directory' is not owned by the executing user. Remedy with `chown -R ".posix_geteuid().":".posix_geteuid()." $directory`");
}
if (ini_get('session.use_trans_sid'))
throw new Flux_Error("The 'session.use_trans_sid' php.ini configuration must be turned off for Flux to work.");
// Installer library.
$installer = Flux_Installer::getInstance();
if ($hasUpdates=$installer->updateNeeded())
Flux::config('ThemeName', array('installer'));
$sessionKey = Flux::config('SessionKey');
$sessionExpireDuration = Flux::config('SessionCookieExpire') * 60 * 60;
$cookie_options = array(
// Session timeout
'lifetime' => $sessionExpireDuration,
// Flux URL
'path' => Flux::config( 'BaseURI' ),
// Domain name for the cookie
'domain' => preg_replace( '/:\d+$/', '', Flux::config( 'ServerAddress' ) ), // Remove port number if present (e.g. "example.com:80")
// Only transfer the cookie via HTTPS
'secure' => Flux::config( 'ForceHTTPS' ),
// Only include the cookie in HTTP requests, making it inaccessible by Javascript
'httponly' => true,
// Only send the cookie to the domain+path defined above
'samesite' => 'Strict'
);
if( !session_set_cookie_params( $cookie_options ) ){
throw new Flux_Error( "Unable to configure the session cookie correctly" );
}
ini_set('session.gc_maxlifetime', $sessionExpireDuration);
ini_set('session.name', $sessionKey);
@session_start();
if (empty($_SESSION[$sessionKey]) || !is_array($_SESSION[$sessionKey])) {
$_SESSION[$sessionKey] = array();
}
// Initialize session data.
Flux::$sessionData = new Flux_SessionData($_SESSION[$sessionKey], $hasUpdates);
// Initialize authorization component.
$accessConfig = Flux::parseConfigFile(FLUX_CONFIG_DIR.'/access.php');
// Merge with add-on configs.
foreach (Flux::$addons as $addon) {
$accessConfig->merge($addon->accessConfig);
}
$accessConfig->set('unauthorized.index', AccountLevel::ANYONE);
$authComponent = Flux_Authorization::getInstance($accessConfig, Flux::$sessionData);
if (Flux::config('DebugMode')) {
error_reporting(E_ALL);
ini_set('display_errors', 1);
}
// Dispatch requests->modules->actions->views.
$dispatcher = Flux_Dispatcher::getInstance();
$dispatcher->setDefaultModule(Flux::config('DefaultModule'));
$dispatcher->dispatch(array(
'basePath' => Flux::config('BaseURI'),
'useCleanUrls' => Flux::config('UseCleanUrls'),
'modulePath' => FLUX_MODULE_DIR,
'themePath' => FLUX_THEME_DIR,
'themeName' => Flux::$sessionData->theme,
'missingActionModuleAction' => Flux::config('DebugMode') ? array('errors', 'missing_action') : array('main', 'page_not_found'),
'missingViewModuleAction' => Flux::config('DebugMode') ? array('errors', 'missing_view') : array('main', 'page_not_found')
));
}
catch (Exception $e) {
$exceptionDir = FLUX_DATA_DIR.'/logs/errors/exceptions';
if (is_writable($exceptionDir)) {
require_once 'Flux/LogFile.php';
$today = date('Ymd');
$eLog = new Flux_LogFile("$exceptionDir/$today.log");
// Log exception.
$eLog->puts('(%s) Exception %s: %s', get_class($e), get_class($e), $e->getMessage());
foreach (explode("\n", $e->getTraceAsString()) as $traceLine) {
$eLog->puts('(%s) **TRACE** %s', get_class($e), $traceLine);
}
}
if(Flux::config('DiscordUseWebhook')) {
if(Flux::config('DiscordSendOnErrorException')) {
sendtodiscord(Flux::config('DiscordWebhookURL'), '```ansi
[2;31mERROR[0m
Error: '. get_class($e) .'
Exception: '. $e->getMessage() .'
File: '. $e->getFile() .':'. $e->getLine() .'```');
}
}
require_once FLUX_CONFIG_DIR.'/error.php';
define('__ERROR__', 1);
include $errorFile;
}
?>
================================================
FILE: lang/en_us.php
================================================
'English',
'YesLabel' => 'Yes',
'NoLabel' => 'No',
'NoteLabel' => 'Note',
'GenderTypeMale' => 'Male',
'GenderTypeFemale' => 'Female',
'GenderTypeServer' => 'Server',
'RefreshSecurityCode' => 'Refresh Security Code',
'NoneLabel' => 'None',
'NeverLabel' => 'Never',
'NotApplicableLabel' => 'Not Applicable',
'UnknownLabel' => 'Unknown',
'IsEqualToLabel' => 'is equal to',
'IsGreaterThanLabel' => 'is greater than',
'IsLessThanLabel' => 'is less than',
'AllLabel' => 'All',
'SearchLabel' => 'Search…',
'GoBackLabel' => 'Go back to previous page…',
'SearchButton' => 'Search',
'ResetButton' => 'Reset',
'FilterButton' => 'Filter',
'NotAcceptingDonations' => "We're sorry, but we are currently not accepting any donations. We apologize for the inconvenience.",
//'NotAcceptingDonations' => "We're sorry, but our donation system is currently undergoing maintenance, please try again later.",
'FoundSearchResults' => 'Found a total of %d record(s) across %d page(s). Displaying result(s) %d-%d.',
'LoginToDonate' => 'Please log-in to make a donation.',
'UnknownCharacter' => 'No such character found.',
'AccountIdLabel' => 'Account ID',
'AccountGroupIDLabel' => 'Group ID',
'AccountStateLabel' => 'State',
'CreditBalanceLabel' => 'Credit Balance',
'UsernameLabel' => 'Username',
'PasswordLabel' => 'Password',
'EmailAddressLabel' => 'E-mail',
'GenderLabel' => 'Gender',
'LoginCountLabel' => 'Login Count',
'LastLoginDateLabel' => 'Last Login Date',
'LastUsedIpLabel' => 'Last Used IP',
'AccountStateNormal' => 'Normal',
'AccountStatePending' => 'Pending',
'AccountStatePermBanned' => 'Permanently Banned',
'AccountStateTempBanLbl' => 'Temporarily Banned',
'AccountStateTempBanned' => 'Temp. Banned (unban: %s)',
'OnlineLabel' => 'Online',
'OfflineLabel' => 'Offline',
'ItemIdLabel' => 'Item ID',
'ItemNameLabel' => 'Item Name',
'ItemAmountLabel' => 'Amount',
'ItemIdentifyLabel' => 'Identified',
'ItemRefineLabel' => 'Refine',
'ItemBrokenLabel' => 'Broken',
'ItemCard0Label' => 'Slot 1',
'ItemCard1Label' => 'Slot 2',
'ItemCard2Label' => 'Slot 3',
'ItemCard3Label' => 'Slot 4',
'ItemRandOptionsLabel' => 'Random options',
//SIDEBAR
//FluxCP Menu Items
//Categories
'MainMenuLabel' => 'Main Menu',
'ForumLabel' => 'Forum',
'AccountLabel' => 'Account',
'CharacterLabel' => 'Character',
'ServiceDeskLabel' => 'Service Desk',
'CPLogsLabel' => 'CP Logs',
'FluxAdminLabel' => 'Flux Admin',
'PagesLabel' => 'Pages',
'IPBanListLabel' => 'IP Ban List',
'GuildsLabel' => 'Guilds',
'rALogsLabel' => 'rA Logs',
'SendMailLabel' => 'Send Mail',
'ReInstallLabel' => 'Re-Install',
'TaskListLabel' => 'Task List',
'DonationsLabel' => 'Donations',
'InformationLabel' => 'Information',
'DatabaseLabel' => 'Database',
'SocialLabel' => 'Social',
//SubMenus
'HomeLabel' => 'Home',
'NewsLabel' => 'News',
'DownloadsLabel' => 'Downloads',
'RulesLabel' => 'Rules',
'ContactUsLabel' => 'Contact Us',
'MyAccountLabel' => 'My Account',
'HistoryLabel' => 'History',
'PurchaseLabel' => 'Purchase',
'DonateLabel' => 'Donate',
'ServerInfoLabel' => 'Server Info',
'ServerStatusLabel' => 'Server Status',
'WoeHoursLabel' => 'WOE Hours',
'CastlesLabel' => 'Castles',
'WhosOnlineLabel' => "Who's Online",
'MapStaticsLabel' => 'Map Statics',
'RankingInfoLabel' => 'Ranking Info',
'VendingInfoLabel' => 'Vending Info',
'BuyingstoreInfoLabel' => 'Buyingstore Info',
'ItemDatabaseLabel' => 'Item Database',
'MobDatabaseLabel' => 'Mob Database',
'JoinUsInFacebookLabel' => 'Join us on Facebook!',
'RateUsOnRMSLabel' => 'Rate us on RMS!',
// Module: account
// - account/changemail
'EmailChangeTitle' => 'Change E-mail',
'EnterEmailAddress' => 'Please enter an e-mail address.',
'EmailCannotBeSame' => 'Your new e-mail cannot be the same as your current.',
'EmailInvalid' => 'Invalid e-mail address.',
'EmailAlreadyRegistered' => "The e-mail address you've entered is already registered to another account.",
'EmailChangeSent' => 'An e-mail has been sent to your new address with a link that will confirm the change.',
'EmailAddressChanged' => 'Your e-mail address has been changed!',
'EmailChangeFailed' => 'Failed to change e-mail address. Please try again later.',
'EmailChangeHeading' => 'Change E-mail',
'EmailChangeInfo' => 'If you would like to change the e-mail address registered under your account, you can fill out the below form.',
'EmailChangeInfo2' => 'After submitting the form, you will be required to confirm your new e-mail address (an e-mail will be sent to the new address with a link).',
'EmailChangeLabel' => 'New E-mail Address',
'EmailChangeInputNote' => 'Must be a valid e-mail address!',
'EmailChangeButton' => 'Change E-mail Address',
// - account/changepass
'PasswordChangeTitle' => 'Change Password',
'NeedCurrentPassword' => 'Please enter your current password.',
'NeedNewPassword' => 'Please enter your new password.',
'OldPasswordInvalid' => "The password you provided doesn't match the one we have on record.",
'ConfirmNewPassword' => 'Please confirm your new password.',
'NewPasswordHasUsername' => 'Your new password must not contain your username.',
'NewPasswordInvalid' => 'Your new password contains invalid characters.',
'NewPasswordSameAsOld' => 'New password cannot be the same as your current password.',
'NewPasswordNeedUpper' => 'Your new password must contain at least %d uppercase letter(s).',
'NewPasswordNeedLower' => 'Your new password must contain at least %d lowercase letter(s).',
'NewPasswordNeedNumber' => 'Your new password must contain at least %d number(s).',
'NewPasswordNeedSymbol' => 'Your new password must contain at least %d symbol(s).',
'PasswordHasBeenChanged' => 'Your password has been changed, please log-in again.',
'FailedToChangePassword' => 'Failed to change your password. Please contact an admin.',
'PasswordChangeHeading' => 'Change Your Password',
'PasswordChangeInfo' => 'Please enter your current password, then enter the new password you would like to use and re-enter it to confirm.',
'CurrentPasswordLabel' => 'Current Password',
'NewPasswordLabel' => 'New Password',
'NewPasswordConfirmLabel' => 'Re-enter New Password',
'PasswordChangeNote' => 'Please be sure to enter the correct information.',
'PasswordChangeNote2' => 'After changing your password, you will be logged out.',
'PasswordChangeButton' => 'Change Password',
// - account/changesex
'GenderChangeTitle' => 'Change Gender',
'GenderChangeBadChars' => 'You cannot change your gender if any of your characters is a: %s',
'GenderChanged' => 'Your gender has been changed and %d credit(s) have been deducted from your account.',
'GenderChangedForFree' => 'Your gender has been changed.',
'GenderChangeHeading' => 'Change Your Gender',
'GenderChangeCost' => 'Gender changes cost %s credit(s).',
'GenderChangeBalance' => 'Your current balance is %s credit(s).',
'GenderChangeNoFunds' => 'You do not have enough credits to perform a gender change at this time.',
'GenderChangeNoCost' => 'For you, gender changes are free.',
'GenderChangeCharInfo' => 'You cannot change gender if you have the follow character jobs: %s',
'GenderChangeSubHeading' => 'Please make sure you want to really change!',
'GenderChangeFormText' => 'Would you like to change your gender to %s?',
'GenderChangeConfirm' => 'Are you absolutely sure you want to change your gender?',
'GenderChangeButton' => 'Yes, do it please.',
// - account/confirm
'AccountConfirmTitle' => 'Confirm Account',
'AccountConfirmUnban' => 'Account has been confirmed and activated.',
'AccountConfirmMessage' => 'Your account has been confirmed and activated, you may now log-in.',
// - account/confirmemail
'EmailConfirmTitle' => 'Confirm E-mail',
'EmailConfirmFailed' => 'There has been a technical difficulty while updating your e-mail address, please contact an admin.',
'EmailConfirmChanged' => 'Your e-mail address has been changed!',
// - account/create
'AccountCreateTitle' => 'Create an Account',
'AccountConfirmBan' => 'Awaiting account activation: %s',
'AccountCreateEmailSent' => 'An e-mail has been sent containing account activation details, please check your e-mail and activate your account to log-in.',
'AccountCreateFailed' => 'Your account has been created, but unfortunately we failed to send an e-mail due to technical difficulties. Please contact a staff member and request for assistance.',
'AccountCreated' => 'Congratulations, you have been registered successfully and automatically logged in.',
'AccountCreateHeading' => 'Register',
'AccountCreateTerms' => 'Terms of Service',
'AccountCreateInfo' => 'Please read our %s (ToS) before registering for an account, to ensure that you understand the rules of holding an account with our private Ragnarok Online game server.',
'AccountCreateInfo2' => 'By clicking "Create My Account", you agree to be bound by our %s.',
'AccountCreateGenderInfo' => "The gender you choose here will affect your in-game character's gender!",
'AccountServerLabel' => 'Server',
'AccountUsernameLabel' => 'Your Username',
'AccountPasswordLabel' => 'Your Password',
'AccountPassConfirmLabel' => 'Confirm Password',
'AccountEmailLabel' => 'E-mail Address',
'AccountEmailLabel2' => 'Confirm E-mail Address',
'AccountGenderLabel' => 'Gender',
'AccountBirthdateLabel' => 'Birthdate',
'AccountSecurityLabel' => 'Security Code',
'AccountCreateButton' => 'Create My Account',
'AccountInvalidChars' => "A username can only contain these characters: '%s'",
'AccountRecaptchaKey' => 'You need Recaptcha keys, see more in config/applications.php (ReCaptchaPublicKey/ReCaptchaPrivateKey)',
'InvalidLoginServer' => 'Invalid login server selected, please try again with a valid server.',
'InvalidLoginCredentials' => 'Invalid login credentials, please verify that you typed the correct info and try again.',
'UnexpectedLoginError' => 'Unexpected error occurred, please try again or report to an admin.',
'CriticalLoginError' => 'Something bad happened. Report to an administrator ASAP.',
'UsernameAlreadyTaken' => "The username you've chosen has already been taken by another user.",
'UsernameTooShort' => sprintf('Your username should be around %d to %d characters long.', Flux::config('MinUsernameLength'), Flux::config('MaxUsernameLength')),
'UsernameTooLong' => sprintf('Your username should be around %d to %d characters long.', Flux::config('MinUsernameLength'), Flux::config('MaxUsernameLength')),
'PasswordContainsUser' => 'Your password cannot contain your username.',
'PasswordHasUsername' => 'Your password must not contain your username.',
'PasswordTooShort' => 'Your password should be around %d to %d characters long.',
'PasswordTooLong' => 'Your password should be around %d to %d characters long.',
'PasswordsDoNotMatch' => "Your passwords do not match, please make sure that you've typed them both correctly.",
'PasswordNeedUpper' => 'Your password must contain at least %d uppercase letter(s).',
'PasswordNeedLower' => 'Your password must contain at least %d lowercase letter(s).',
'PasswordNeedNumber' => 'Your password must contain at least %d number(s).',
'PasswordNeedSymbol' => 'Your password must contain at least %d symbol(s).',
'EmailAddressInUse' => "The e-mail address you've entered is already registered to another account. Please use a different e-mail address.",
'InvalidEmailAddress' => "The e-mail address you've entered is not in a valid e-mail address format.",
'InvalidEmailconf' => "E-mail addresses do not match.",
'InvalidGender' => 'Gender should be "M" or "F"',
'InvalidServer' => "The server you've selected does not exist.",
'InvalidSecurityCode' => 'Please enter the security code correctly.',
'InvalidPassword' => 'Your password contains invalid characters.',
'InvalidBirthdate' => 'Invalid birthdate input.',
'CriticalRegisterError' => 'Something bad happened. Report to an administrator ASAP.',
// - account/edit
'AccountEditTitle' => 'Modify Account',
'AccountEditTitle2' => 'Modifying My Account',
'AccountEditTitle3' => 'Modifiying Account (%s)',
'CannotModifyOwnGroupID' => 'You cannot modify your own account group ID.',
'CannotModifyAnyGroupID' => 'You cannot modify account group IDs.',
'CannotModifyGroupIDHigh' => 'You cannot set an account group ID to be higher than your own.',
'InvalidGroupID' => 'Invalid group ID.',
'CannotModifyBalance' => 'You cannot modify account balances.',
'InvalidLastLoginDate' => 'Invalid last login date.',
'InvalidVIPTime' => 'Invalid VIP Time.',
'AccountModified' => 'Account has been modified.',
'AccountEditHeading' => 'Modify Account',
'AccountEditButton' => 'Modify Account',
'AccountEditNotFound' => 'No such account.',
'VIPTimeDateLabel' => 'VIP Until',
// - account/index
'AccountIndexTitle' => 'List Accounts',
'AccountIndexHeading' => 'Accounts',
'LoginBetweenLabel' => 'Login Between',
'BirthdateBetweenLabel' => 'Birthdate Between',
'AccountIndexNotFound' => 'No such account.',
// - account/login
'LoginTitle' => 'Log In',
'LoginHeading' => 'Log In',
'LoginButton' => 'Log In',
'LoginPageMakeAccount' => 'Don\'t have an account? Create one!',
'TemporarilyBanned' => 'Your account is temporarily banned.',
'PermanentlyBanned' => 'Your account is permanently banned.',
'IpBanned' => 'The IP address you are behind is banned.',
'PendingConfirmation' => 'Your account is pending e-mail confirmation.',
// - account/logout
'LogoutTitle' => 'Logout',
'LogoutHeading' => 'Logout',
'LogoutInfo' => 'You are now logged out.',
'LogoutInfo2' => 'Please wait a moment while you are redirected…',
// - account/resend
'ResendTitle' => 'Resend Confirmation E-mail',
'ResendEnterUsername' => 'Please enter your account username.',
'ResendEnterEmail' => 'Please enter your e-mail address.',
'ResendFailed' => 'Failed to resend confirmation code.',
'ResendEmailSent' => 'Your confirmation code has been resent, please check your e-mail and proceed to activate your account.',
'ResendHeading' => 'Resend Confirmation E-mail',
'ResendInfo' => 'Please enter your account name and e-mail address you used during the registration of the account to have us resend your confirmation e-mail.',
'ResendServerLabel' => 'Registered Server',
'ResendAccountLabel' => 'Account Username',
'ResendEmailLabel' => 'E-mail Address',
'ResendServerInfo' => 'This is the server the account was registered on.',
'ResendAccountInfo' => 'This is the account name you registered.',
'ResendEmailInfo' => 'This is the e-mail address you used during the registration of the above account.',
'ResendButton' => 'Resend Confirmation E-mail',
// - account/resetpass
'ResetPassTitle' => 'Reset Password',
'ResetPassEnterAccount' => 'Please enter your account username.',
'ResetPassEnterEmail' => 'Please enter your e-mail address.',
'ResetPassDisallowed' => 'Password recovery cannot be used for this account.',
'ResetPassFailed' => 'Failed to send reset password e-mail.',
'ResetPassEmailSent' => 'An e-mail has been sent with details on how to reset your password.',
'ResetPassInfo' => 'If you lost your password, you can re-set it by entering the e-mail address you used to register your account.',
'ResetPassInfo2' => 'An e-mail will then be sent to the specified address with a link allowing you to reset your password, therefore a valid e-mail address is required.',
'ResetPassServerLabel' => 'Registered Server',
'ResetPassAccountLabel' => 'Account Username',
'ResetPassEmailLabel' => 'E-mail Address',
'ResetPassServerInfo' => 'This is the server the account was registered on.',
'ResetPassAccountInfo' => 'This is the account name you registered.',
'ResetPassEmailInfo' => 'This is the e-mail address you used during the registration of the above account.',
'ResetPassButton' => 'Send Reset Password E-mail',
// - account/resetpw
'ResetPwTitle' => 'Reset Password',
'ResetPwFailed' => 'Failed to re-set password, please try again later.',
'ResetPwDone' => 'Your password has been reset and an e-mail containing your new password has been sent to you.',
'ResetPwDone2' => 'Your password has been reset, but we failed to deliver the e-mail containing your new password. Please reset again to resolve this issue.',
// - account/transfer
'TransferTitle' => 'Transfer Donation Credits',
'TransferGreaterThanOne' => 'You can only transfer credits in amounts greater than 1.',
'TransferEnterCharName' => 'You must input the character name of who will receive the credits.',
'TransferNoCharExists' => "Character '%s' does not exist. Please make sure you typed it correctly.",
'TransferNoBalance' => 'You do not have a sufficient balance to make the transfer.',
'TransferUnexpectedError' => 'Unexpected error occurred.',
'TransferSuccessful' => 'Credits have been transferred!',
'TransferHeading' => 'Transfer Donation Credits',
'TransferSubHeading' => 'Credits will be transferred to a character on the %s server.',
'TransferInfo' => 'You currently have %s credit(s).',
'TransferInfo2' => 'Enter the amount you would like to transfer and the character name belonging to the account you would like your credits transferred to.',
'TransferAmountLabel' => 'Amount of Credits',
'TransferCharNameLabel' => 'Character Name',
'TransferAmountInfo' => 'This is the amount of credits you would like to send.',
'TransferCharNameInfo' => 'This is the character name of who will be receiving the credits.',
'TransferConfirm' => 'Are you sure you want to do this?',
'TransferButton' => 'Transfer',
'TransferNoCredits' => 'You have no credits available in your account.',
// - account/view
'VIPStateLabel' => 'VIP Status',
// * account/view submenus
'ModifyAccountLink' => 'Modify Account',
'AccountViewTitle' => 'View Account',
'AccountViewTitle2' => 'Viewing Account (%s)',
'AccountViewTitle3' => 'Viewing My Account',
'AccountTempBanFailed' => 'Failed to temporarily ban account.',
'AccountPermBanFailed' => 'Failed to permanently ban account.',
'AccountTempBanUnauth' => 'You are unauthorized to place temporary bans on this account.',
'AccountPermBanUnauth' => 'You are unauthorized to place permanent bans on this account.',
'AccountLiftTempBan' => 'Account has been unbanned.',
'AccountLiftPermBan' => 'Account has been unbanned.',
'AccountLiftBanUnauth' => "You are unauthorized to remove this account's ban status.",
'AccountViewHeading' => 'Viewing Account',
'AccountViewDonateLink' => '(Donate!)',
'AccountViewTempBanLabel' => 'Temporary Ban',
'AccountViewPermBanLabel' => 'Permanent Ban',
'AccountViewUnbanLabel' => 'Remove Ban',
'AccountBanReasonLabel' => 'Reason:',
'AccountBanUntilLabel' => 'Ban Until:',
'AccountTempBanButton' => 'Ban Account',
'AccountPermBanButton' => 'Permanently Ban Account',
'AccountTempUnbanButton' => 'Remove Temporary Ban',
'AccountPermUnbanButton' => 'Remove Permanent Ban',
'AccountBanConfirm' => 'Are you sure?',
'AccountBanLogSubHeading' => 'Ban Log for %s (recent to oldest)',
'BanLogBanTypeLabel' => 'Ban Type',
'BanLogBanDateLabel' => 'Ban Date',
'BanLogBanReasonLabel' => 'Ban Reason',
'BanLogBannedByLabel' => 'Banned By',
'BanLogBannedByCP' => 'Control Panel',
'BanTypeUnbanned' => 'Unbanned',
'BanTypePermBanned' => 'Permanently Banned',
'BanTypeTempBanned' => 'Temporarily Banned',
'AccountViewCharSubHead' => 'Characters on %s',
'AccountViewSlotLabel' => 'Slot',
'AccountViewCharLabel' => 'Character Name',
'AccountViewClassLabel' => 'Job Class',
'AccountViewLvlLabel' => 'Base Level',
'AccountViewJlvlLabel' => 'Job Level',
'AccountViewZenyLabel' => 'Zeny',
'AccountViewGuildLabel' => 'Guild',
'AccountViewStatusLabel' => 'Status',
'AccountViewPrefsLabel' => 'Preferences',
'CharModifyPrefsLink' => 'Modify Preferences',
'AccountViewNoChars' => 'This account has no characters on %s.',
'AccountViewStorage' => 'Storage Items of %s',
'AccountViewStorageCount' => '%s has %s storage item(s).',
'AccountViewNoStorage' => 'There are no storage items on this account.',
'AccountViewNotFound' => "Records indicate that the account you're trying to view does not exist.",
// - account/xferlog
'XferLogTitle' => 'Credit Transfer History',
'XferLogHeading' => 'Credit Transfer History',
'XferLogReceivedSubHead' => 'Transfers: Received',
'XferLogSentSubHead' => 'Transfers: Sent',
'XferLogCreditsLabel' => 'Credits',
'XferLogFromLabel' => 'From E-mail',
'XferLogDateLabel' => 'Transfer Date',
'XferLogCharNameLabel' => 'To Character',
'XferLogNotReceived' => 'You have not received any credit transfers.',
'XferLogNotSent' => 'You have not sent any credit transfers.',
// Module: character
// - character/changeslot
// - character/index
// - character/mapstats
// - character/online
// - character/prefs
// - character/resetlook
'CantResetLookWhenOnline' => 'Cannot reset look while %s is online.',
'ResetLookSuccessful' => "%s's look has been reset!",
'ResetLookFailed' => "Failed to reset %s's look.",
// - character/resetpos
'CantResetPosWhenOnline' => 'Cannot reset position while %s is online.',
'CantResetFromCurrentMap' => "You cannot reset %s's position from the current map.",
'ResetPositionSuccessful' => "%s's position has been reset!",
'ResetPositionFailed' => "Failed to reset %s's position.",
// - character/view
// - character/divorce
'DivorceTitle' => 'Divorce',
'DivorceHeading' => 'Divorce',
'DivorceNotMarried' => '%s is not married.',
'DivorceInvalidPartner' => 'Invalid partner ID.',
'DivorceInvalidChild' => 'Invalid child ID.',
'DivorceMustBeOffline' => 'Both %s and his/her partner must be off-line.',
'DivorceMustBeOffline2' => '%s, his/her partner and their child must be off-line.',
'DivorceText1' => "Are you sure you want to divorce %s and his/her partner?",
'DivorceText2' => 'If %s has a child, the child will also be orphaned.',
'DivorceText3' => 'Wedding rings will also be deleted.',
'DivorceButton' => 'Yes, do it please.',
'DivorceSuccessful' => '%s has been divorced!',
// Module: cplog
// - cplog/index.php
// - cplog/login.php
// - cplog/paypal.php
// - cplog/resetpass.php
// - cplog/txnview.php
// Module: donate
// - donate/complete
// - donate/history
// - donate/index
// - donate/trusted
// Module: errors
// - errors/missing_action
'MissingActionTitle' => 'Missing Action',
'MissingActionHeading' => 'Missing Action!',
'MissingActionModLabel' => 'Module:',
'MissingActionActLabel' => 'Action:',
'MissingActionReqLabel' => 'Request URI:',
'MissingActionLocLabel' => 'File system location:',
// - errors/missing_view
'MissingViewTitle' => 'Missing View',
'MissingViewHeading' => 'Missing View!',
'MissingViewModLabel' => 'Module:',
'MissingViewActLabel' => 'Action:',
'MissingViewReqLabel' => 'Request URI:',
'MissingViewLocLabel' => 'File system location:',
// Module: guild
// - guild/export
// - guild/index
// - guild/view
// Module: history
// - history/cplogin
'HistoryCpLoginTitle' => 'Control Panel Logins',
'HistoryCpLoginHeading' => 'Control Panel Logins',
'HistoryLoginDateLabel' => 'Login Date/Time',
'HistoryIpAddrLabel' => 'IP Address',
'HistoryErrorCodeLabel' => 'Error Code',
'HistoryNoCpLogins' => 'No control panel login attempts found.',
// -history/emailchange
'HistoryEmailTitle' => 'E-Mail Changes',
'HistoryEmailHeading' => 'E-Mail Changes',
'HistoryEmailRequestDate' => 'Request Date/Time',
'HistoryEmailRequestIp' => 'Request IP',
'HistoryEmailOldAddress' => 'Old E-Mail',
'HistoryEmailNewAddress' => 'New E-Mail',
'HistoryEmailChangeDate' => 'Change Date',
'HistoryEmailChangeIp' => 'Change IP',
'HistoryNoEmailChanges' => 'No e-mail change attempts found.',
// - history/gamelogin
'HistoryGameLoginTitle' => 'Game Logins',
'HistoryGameLoginHeading' => 'Game Logins',
'HistoryRepsCodeLabel' => 'Response Code',
'HistoryLogMessageLabel' => 'Log Message',
'HistoryNoGameLogins' => 'No in-game login attempts found.',
// - history/index
'HistoryIndexTitle' => 'My Account History',
'HistoryIndexHeading' => 'My Account History',
'HistoryIndexInfo' => 'Here you can view past account activity of your account.',
'HistoryIndexInfo2' => 'Please select an action from the menu.',
// - history/passchange
'HistoryPassChangeTitle' => 'Password Changes',
'HistoryPassChangeHeading' => 'Password Changes',
'HistoryPassChangeChangeDate' => 'Change Date',
'HistoryPassChangeChangeIp' => 'Change IP',
'HistoryNoPassChanges' => 'No password changes found.',
// -history/passreset
'HistoryPassResetTitle' => 'Password Resets',
'HistoryPassResetHeading' => 'Password Resets',
'HistoryPassResetRequestDate' => 'Request Date/Time',
'HistoryPassResetRequestIp' => 'Request IP',
'HistoryPassResetResetDate' => 'Reset Date',
'HistoryPassResetResetIp' => 'Reset IP',
'HistoryNoPassResets' => 'No password reset attempts found.',
// Module: ipban
// - ipban/add
'IpbanAddTitle' => 'Add IP Ban',
'IpbanEnterIpPattern' => 'Please input an IP address or pattern.',
'IpbanInvalidPattern' => 'Invalid IP address or pattern.',
'IpbanWhitelistedPattern' => 'This pattern is whitelisted and cannot be blocked.',
'IpbanEnterReason' => 'Please enter a reason for the IP ban.',
'IpbanSelectUnbanDate' => 'Unban date is required.',
'IpbanFutureDate' => 'Unban date must be specified to a future date.',
'IpbanAlreadyBanned' => 'A matching IP (%s) has already been banned.',
'IpbanPatternBanned' => "The IP address/pattern '%s' has been banned.",
'IpbanAddFailed' => 'Failed to add IP ban.',
'IpbanAddHeading' => 'Add IP Ban',
'IpbanIpAddressLabel' => 'IP Address',
'IpbanReasonLabel' => 'Ban Reason',
'IpbanUnbanDateLabel' => 'Unban Date',
'IpbanIpAddressInfo' => 'You may specify a pattern such as 218.139.*.*',
'IpbanAddButton' => 'Add IP Ban',
// - ipban/edit
'IpbanEditTitle' => 'Modify IP Ban',
'IpbanEnterEditReason' => 'Please enter a reason for the IP ban modification.',
'IpbanEditFailed' => 'Failed to modify IP ban.',
'IpbanEditHeading' => 'Modify IP Ban',
'IpbanEditReasonLabel' => 'Edit Reason',
'IpbanEditButton' => 'Modify IP Ban',
// - ipban/index
'IpbanListTitle' => 'IP Ban List',
'IpbanListHeading' => 'IP Ban List',
'IpbanBannedIpLabel' => 'Banned IP',
'IpbanBanDateLabel' => 'Ban Date',
'IpbanBanReasonLabel' => 'Ban Reason',
'IpbanBanExpireLabel' => 'Ban Expiration Date',
'IpbanModifyLink' => 'Modify',
'IpbanRemoveLink' => 'Remove',
'IpbanUnbanButton' => 'Unban Selected',
'IpbanListNoBans' => 'There are currently no IP bans.',
// - ipban/remove
'IpbanRemoveTitle' => 'Remove IP Ban',
'IpbanEnterRemoveReason' => 'Please enter a reason for the IP ban removal.',
'IpbanNotBanned' => 'No matching IP (%s) is currently banned.',
'IpbanPatternUnbanned' => "The IP address/pattern '%s' has been unbanned.",
'IpbanRemoveFailed' => 'Failed to remove IP ban.',
'IpbanRemoveHeading' => 'Remove IP Ban',
'IpbanRemoveReasonLabel' => 'Unban Reason',
'IpbanRemoveButton' => 'Remove IP Ban',
// - ipban/unban
'IpbanNothingToUnban' => 'Nothing to unban.',
'IpbanEnterUnbanReason' => 'Please enter a reason for lifting the IP ban(s).',
'IpbanUnbanned' => 'Lifted selected IP ban(s)!',
'IpbanUnbanFailed' => 'Failed to lift %d of the specified IP unban(s)!',
// Module: item
// - item/add
// - item/copy
// - item/edit
// - item/index
// - item/view
// Module: itemshop
// - itemshop/add
// - itemshop/delete
// - itemshop/edit
// - itemshop/imagedel
// Module: logdata
// - logdata/chat
// - logdata/cashpoints
'CashLogTitle' => 'List CashPoints Log',
'CashLogHeading' => 'CashPoint Log',
'CashLogNotFound' => 'No cash logs found.',
'CashLogDateLabel' => 'Date/Time',
'CashLogCharacterLabel' => 'Character',
'CashLogTypeLabel' => 'Type',
'CashLogCashTypeLabel' => 'Cash Type',
'CashLogAmountLabel' => 'Amount',
'CashLogMapLabel' => 'Map',
// - logdata/command
'CommandLogTitle' => 'List Commands',
'CommandLogHeading' => 'Commands',
'CommandLogNotFound' => 'No commands found.',
'CommandLogDateLabel' => 'Date/Time',
'CommandLogAccountIdLabel'=> 'Account ID',
'CommandLogCharIdLabel' => 'Character ID',
'CommandLogCharNameLabel' => 'Character Name',
'CommandLogCommandLabel' => 'Command',
'CommandLogMapLabel' => 'Map',
// - logdata/index
// - logdata/login
// - logdata/pick
'PickLogTitle' => 'List Item Picks',
'PickLogHeading' => 'Item Picks',
'PickLogNotFound' => 'No item picks found.',
'PickLogDateLabel' => 'Date/Time',
'PickLogCharacterLabel' => 'Character',
'PickLogTypeLabel' => 'Type',
'PickLogItemLabel' => 'Item Name',
'PickLogAmountLabel' => 'Amount',
'PickLogRefineLabel' => 'Refine',
'PickLogCard0Label' => 'Slot 1',
'PickLogCard1Label' => 'Slot 2',
'PickLogCard2Label' => 'Slot 3',
'PickLogCard3Label' => 'Slot 4',
'PickLogMapLabel' => 'Map',
// - logdata/branch
'BranchLogTitle' => 'List Branch Log',
'BranchLogHeading' => 'Branch Log',
'BranchLogNotFound' => 'No branch logs found.',
'BranchLogIDLabel' => 'Branch Log ID',
'BranchLogDateLabel' => 'Date / Time',
'BranchLogAccountIDLabel' => 'Account ID',
'BranchLogCharIDLabel' => 'Char ID',
'BranchLogCharNameLabel' => 'Char Name',
'BranchLogMapLabel' => 'Map',
// - logdata/char
'CharLogTitle' => 'List Character Log',
'CharLogHeading' => 'Character Log',
'CharLogNotFound' => 'No character logs found.',
'CharLogDateLabel' => 'Date / Time',
'CharLogMsgLabel' => 'Action',
'CharLogAccountIDLabel' => 'Account ID',
'CharLogCharNumLabel' => 'Character slot',
'CharLogCharNameLabel' => 'Character Name',
// - logdata/inter
'InterLogTitle' => 'List of Interactions Log',
'InterLogHeading' => 'Interactions Log',
'InterLogNotFound' => 'No Interactions logs found.',
'InterLogDateLabel' => 'Date / Time',
'InterLogLabel' => 'Interactions Log',
// - logdata/mvp
'MVPLogTitle' => 'List MVP Log',
'MVPLogHeading' => 'MVP Log',
'MVPLogNotFound' => 'No MVP logs found.',
'MVPLogIDLabel' => 'MVP Log ID',
'MVPLogDateLabel' => 'Date / Time',
'MVPLogCharacterLabel' => 'Character ID',
'MVPLogMonsterLabel' => 'MVP Monster',
'MVPLogPrizeLabel' => 'MVP Prize',
'MVPLogExpLabel' => 'MVP Experience',
'MVPLogMapLabel' => 'Map',
// - logdata/npc
'NPCLogTitle' => 'List NPC Log',
'NPCLogHeading' => 'NPC Log',
'NPCLogNotFound' => 'No npc logs found.',
'NPCLogIDLabel' => 'NPC ID',
'NPCLogDateLabel' => 'Date / Time',
'NPCLogAccountIDLabel' => 'Account ID',
'NPCLogCharIDLabel' => 'Character ID',
'NPCLogCharNameLabel' => 'Character Name',
'NPCLogMapLabel' => 'Map',
'NPCLogMsgLabel' => 'Message',
// - logdata/zeny
'ZenyLogTitle' => 'List Zeny Log',
'ZenyLogHeading' => 'Zeny Log',
'ZenyLogNotFound' => 'No zeny logs found.',
'ZenyLogDateLabel' => 'Date/Time',
'ZenyLogCharacterLabel' => 'Character',
'ZenyLogSourceLabel' => 'Source',
'ZenyLogTypeLabel' => 'Type',
'ZenyLogAmountLabel' => 'Amount',
'ZenyLogMapLabel' => 'Map',
// - logdata/feeding
'FeedingLogTitle' => 'Feeding Log',
// Module: mail
// - mail/index
'MailerTitle' => 'Form Mailer',
'MailerHeading' => 'Form Mailer',
'MailerEnterToAddress' => 'Please enter a "to" address.',
'MailerEnterSubject' => 'Please enter a subject.',
'MailerEnterBodyText' => 'Please enter some body text.',
'MailerEmailHasBeenSent' => 'Your e-mail has been successfully sent.',
'MailerFailedToSend' => 'The mailer system failed to send the e-mail. This could be a misconfiguration.',
'MailerInfo' => 'You may use the below mail form to send an e-mail using the control panel.',
'MailerFromLabel' => 'From',
'MailerToLabel' => 'Where are we sending this?',
'MailerSubjectLabel' => 'Subject',
'MailerBodyLabel' => 'Body',
'MailerSelectTemplateLabel' => 'Select Template',
// Module: main
// - main/index
'MainPageHeading' => 'Flux Control Panel',
'MainPageInfo' => "If you are seeing this page, it's likely that you've successfully installed Flux.",
'MainPageInfo2' => "Would you like to change this page? Well, here's how you can:",
'MainPageStep1' => 'Open "%s" in your text editor.',
'MainPageStep2' => 'Edit the file from your editor and save your changes.',
'MainPageThanks' => 'Thanks for using Flux!',
'MainPageWelcome' => 'Welcome to %s!',
// - main/pagenotfound
'PageNotFoundTitle' => '404 Page Not Found',
'PageNotFoundHeading' => 'Page Not Found',
'PageNotFoundInfo' => 'The page you have requested was not found on our server. Please check the address and make sure it is correct, and try again.',
// - main/preprocess
'DisallowedDuringWoE' => 'The page you have requested is not accessible during WoE.',
// Module: monster
// - monster/index
// - monster/view
// Module: purchase
// - purchase/add
// - purchase/cart
// - purchase/checkout
// - purchase/clear
// - purchase/index
// - purchase/pending
// - purchase/remove
// Module: ranking
// - ranking/character
// - ranking/guild
// - ranking/zeny
// Module: server
// - server/info
'ServerInfoTitle' => 'Server Information',
'ServerInfoHeading' => 'Server Information',
'ServerInfoText' => "Here you'll find various server information.",
'ServerInfoSubHeading' => 'Information for %s',
'ServerInfoSubHeading2' => 'Job Class Information for %s',
'ServerInfoAccountLabel' => 'Accounts',
'ServerInfoCharLabel' => 'Characters',
'ServerInfoGuildLabel' => 'Guilds',
'ServerInfoPartyLabel' => 'Parties',
'ServerInfoZenyLabel' => 'Zeny',
// - server/status
'ServerStatusTitle' => 'Current Server Status',
'ServerStatusHeading' => 'Server Status',
'ServerStatusInfo' => "Understanding the online and offline status of each server can help you understand how an issue can relate to your problem. For example, if the login server is offline it means that you won't be able to log into the game. The character server and map servers are necessary for the actual gameplay past the point of logging in.",
'ServerStatusServerLabel' => 'Server',
'ServerStatusLoginLabel' => 'Login Server',
'ServerStatusCharLabel' => 'Character Server',
'ServerStatusMapLabel' => 'Map Server',
'ServerStatusOnlineLabel' => 'Players Online',
'ServerStatusPeakLabel' => 'Player Peak',
// Module: service
// - service/tos
'TermsTitle' => 'Terms of Service',
'TermsHeading' => 'Terms of Service',
'TermsInfo' => 'Please read before creating an account!',
'TermsInfo2' => "FOR CONTROL PANEL ADMINISTRATOR: You may add your server's ToS in this view file directly. The location of the view file is: %s",
// Module: unauthorized
// - unauthorized/index
'UnauthorizedTitle' => 'Unauthorized',
'UnauthorizedHeading' => 'Unauthorized',
'UnauthorizedInfo' => 'You are unauthorized to view this page. Redirecting…',
// Module: woe
// - woe/index
'WoeTitle' => 'WoE Hours',
'WoeHeading' => 'War of Emperium Hours',
'WoeInfo' => "Below are the WoE hours for %s. These hours are subject to change at anytime, but let's hope not.",
'WoeServerTimeInfo' => 'The current server time is:',
'WoeServerLabel' => 'Servers',
'WoeTimesLabel' => 'War of Emperium Times',
'WoeNotScheduledInfo' => 'There are no scheduled WoE hours.',
// Module: contactform
'CFTitleSubmit' => 'Contact Us',
// Module: News and Pages
'CMSNewsHeader' => 'Announcements',
'CMSPageHeader' => 'Content Management System',
'CMSPageText' => 'This module enables server admins and staff to create pages within their website with no prior flux/coding knowledge. The built-in news system can also be swapped out for an rss feed by modifying the application settings.',
'CMSNewsTitleError' => 'News title is required!',
'CMSNewsBodyError' => 'News body is required!',
'CMSPageTitleError' => 'Page Title is required!',
'CMSPageBodyError' => 'Page body is required!',
'CMSPagePathError' => 'Page path is required!',
'CMSNewsAdded' => 'News added to system',
'CMSPagesAdded' => 'Your new page has been added',
'CMSNewsUpdated' => 'News updated',
'CMSPageUpdated' => 'Your page has been updated',
'CMSNewsAddTitle' => 'Add a news item',
'CMSPageAddTitle' => 'Add a new page',
'CMSNewsEditTitle' => 'Edit news',
'CMSPageEditTitle' => 'Edit page',
'CMSNewsNotFound' => 'News not found!',
'CMSPageNotFound' => 'Page not found!',
'CMSNewsDeleted' => 'News deleted',
'CMSPageDeleted' => 'Your page has been deleted',
'CMSNewsEmpty' => 'No news articles have been found. Are you using the correct News Type? (CMSNewsType setting)',
'CMSNewsRSSNotFound' => 'RSS feed can\'t be found. Make sure the CMSNewsRSS setting is correct, or switch CMSNewsType to 1 to use built-in news system!',
'CMSPageEmpty' => 'No page added',
'CMSNewsLink' => 'read more...',
'CMSEdit' => 'Edit',
'CMSDelete' => 'Delete',
'CMSNewsTitleLabel' => 'News Title',
'CMSNewsBodyLabel' => 'News Body',
'CMSNewsLinkLabel' => 'News Link',
'CMSNewsAuthorLabel' => 'News Author',
'CMSPageTitleLabel' => 'Page Title',
'CMSPageBodyLabel' => 'Page Body',
'CMSPagePathLabel' => 'Page Path',
'CMSCreatedLabel' => 'Date Created',
'CMSModifiedLabel' => 'Date Modified',
'CMSActionLabel' => 'Action',
'CMSConfirmDeleteLabel' => 'Are you sure you want to delete?',
'CMSPageCreate' => 'Create now?',
'CMSOptionalLabel' => '(Optional)',
'CMSRequiredLabel' => '(Required)',
'CMSCreateLabel' => 'Add News',
// Module: vending
'TLHeaderTasks' => 'Tasks',
'TLHeaderOwner' => 'Owner',
'TLHeaderPriority' => 'Priority',
'TLHeaderStatus' => 'Status',
'TLHeaderCreated' => 'Created',
'TLHeaderModified' => 'Modified',
'TLHeaderResources' => 'Additional Resources',
'TLHeaderBody' => 'Body',
// Module: servicedesk
'SDHeader' => 'Service Desk',
'SDCreateNew' => 'Create a new ticket',
'SDWelcomeText' => 'Welcome to the Service Desk',
'SDNoTickets' => 'You have not created any tickets.',
'SDNoBlankResponse' => 'For a response to register, you must type something into the box!',
'SDNoCatsAvailable' => 'No Categories Available',
'SDNoOpenTickets' => 'There are no Open tickets in the database.',
'SDNoInactiveTickets' => 'You have no in-active tickets at the moment.',
'SDNoClosedTickets' => 'There are no Closed tickets in the database.',
'SDNoCats' => 'There are no categories within the database.',
'SDHuh' => 'You should not be here o.O',
'SDPointerChatLog' => 'We recommend pasting to pastebin.com then putting the link in here.',
'SDPointerScreenShot' => 'Provide image links as evidence',
'SDPointerVideoLink' => 'We recommend uploading to youtube then putting the link in here.',
'SDHeaderID' => 'Ticket #',
'SDHeaderSubject' => 'Subject',
'SDHeaderCategory' => 'Category',
'SDHeaderStatus' => 'Current Status',
'SDHeaderLastAuthor' => 'Last Author',
'SDHeaderTimestamp' => 'Created',
'SDHeaderAccount' => 'Account',
'SDHeaderTeam' => 'Team',
'SDH3ActiveTickets' => 'Active Tickets',
'SDH3InActiveTickets' => 'In-Active Tickets',
'SDH3ClosedTickets' => 'Closed Tickets',
'SDH3CurrentCat' => 'Current Categories',
'SDH3CreateCat' => 'Create New Category',
'SDH3StaffList' => 'Current Staff Settings',
'SDH3StaffCreate' => 'Add Staff Settings',
'SDReOpenPlayer' => 'Ticket Re-Opened by player',
'SDReOpenStaff' => '',
'SDRespTable1' => 'Respond and Return to Ticket',
'SDRespTable2' => 'Respond and Return to List',
'SDRespTable3' => 'Respond and Resolve Ticket',
'SDRespTable4' => 'Respond and Escalate',
'SDRespTable5' => 'Close Ticket',
'SDRespTable6' => 'Respond and Re-Open Ticket',
'SDRespTable7' => 'Resolve Ticket and Credit Account',
'SDGroup1' => 'Support Staff',
'SDGroup2' => 'Head of Support',
'SDGroup3' => 'Administration',
'SDLinkOpenNew' => 'Open a new ticket',
// Module: webcommands
'WCTitleLabel' => 'Web Commands',
);
?>
================================================
FILE: lang/es_es.php
================================================
almacén ? No! That'd require scripts to be translated for that!
// Example: ban -> expulsión -> bloqueo ? Yes! No scripts to be translated and it's widely understood!
// ip ban -> IP con acceso prohibido ?
return array(
// Generic/Misc.
'Language' => 'Spanish',
'YesLabel' => 'Sí',
'NoLabel' => 'No',
'NoteLabel' => 'Nota',
'GenderTypeMale' => 'Hombre',
'GenderTypeFemale' => 'Mujer',
'GenderTypeServer' => 'Servidor',
'RefreshSecurityCode' => 'Actualizar Código de Seguridad',
'NoneLabel' => 'Ninguno',
'NeverLabel' => 'Nunca',
'NotApplicableLabel' => 'No Aplicable',
'UnknownLabel' => 'Desconocido',
'IsEqualToLabel' => 'es igual que',
'IsGreaterThanLabel' => 'es mayor que',
'IsLessThanLabel' => 'es menor que',
'AllLabel' => 'Todo',
'SearchLabel' => 'Buscar…',
'GoBackLabel' => 'Volver a la página anterior…',
'SearchButton' => 'Buscar',
'ResetButton' => 'Reiniciar',
'FilterButton' => 'Filtrar',
'NotAcceptingDonations' => "En estos momentos no aceptamos donacioes. Disculpa las molestias.",
//'NotAcceptingDonations' => "We're sorry, but our donation system is currently undergoing maintenance, please try again later.",
'FoundSearchResults' => 'Se han encontrado un total de %d registro(s) en %d página(s). Mostrando el/los resultado(s) %d-%d.',
'LoginToDonate' => 'Por favor, inicia sesión para hacer donaciones.',
'UnknownCharacter' => 'No se ha encontrado el personaje.',
'AccountIdLabel' => 'ID de Cuenta',
'AccountGroupIDLabel' => 'ID de Grupo',
'AccountStateLabel' => 'Estado de la Cuenta',
'CreditBalanceLabel' => 'Balance de Crédito',
'UsernameLabel' => 'Nombre de Usuario',
'PasswordLabel' => 'Contraseña',
'EmailAddressLabel' => 'Correo Electrónico',
'GenderLabel' => 'Sexo',
'LoginCountLabel' => 'Número de Conexiones',
'LastLoginDateLabel' => 'Fecha de la Última Conexión',
'LastUsedIpLabel' => 'Última IP Usada',
'AccountStateNormal' => 'Normal',
'AccountStatePending' => 'Pendiente',
'AccountStatePermBanned' => 'Bloqueado Permanentemente',
'AccountStateTempBanLbl' => 'Bloqueado Temporalmente',
'AccountStateTempBanned' => 'Bloqueado Temp. (hasta: %s)',
'OnlineLabel' => 'Conectado',
'OfflineLabel' => 'Desconectado',
'ItemIdLabel' => 'ID del Objeto',
'ItemNameLabel' => 'Nombre del Objeto',
'ItemAmountLabel' => 'Cantidad',
'ItemIdentifyLabel' => 'Identificado',
'ItemRefineLabel' => 'Refinado',
'ItemBrokenLabel' => 'Roto',
'ItemCard0Label' => 'Carta 1',
'ItemCard1Label' => 'Carta 2',
'ItemCard2Label' => 'Carta 3',
'ItemCard3Label' => 'Carta 4',
//SIDEBAR
//FluxCP Menu Items
//Categories
'MainMenuLabel' => 'Menú Principal',
'ForumLabel' => 'Forum',
'AccountLabel' => 'Cuenta',
'CharacterLabel' => 'Personaje',
'ServiceDeskLabel' => 'Service Desk',
'CPLogsLabel' => 'CP Logs',
'FluxAdminLabel' => 'Flux Admin',
'PagesLabel' => 'Pages',
'IPBanListLabel' => 'IP Ban List',
'GuildsLabel' => 'Guilds',
'rALogsLabel' => 'rA Logs',
'SendMailLabel' => 'Send Mail',
'ReInstallLabel' => 'Re-Install',
'TaskListLabel' => 'Task List',
'DonationsLabel' => 'Donaciones',
'InformationLabel' => 'Informaciones',
'DatabaseLabel' => 'Base de datos',
'SocialLabel' => 'Social',
//SubMenus
'HomeLabel' => 'Inicio',
'NewsLabel' => 'Noticias',
'DownloadsLabel' => 'Descargas',
'RulesLabel' => 'Reglas',
'ContactUsLabel' => 'Contáctanos',
'MyAccountLabel' => 'Mi Cuenta',
'HistoryLabel' => 'Historial',
'ServiceDeskLabel' => 'Centro de Soporte',
'PurchaseLabel' => 'Canjear',
'DonateLabel' => 'Donar',
'ServerInfoLabel' => 'Info. del Servidor',
'ServerStatusLabel' => 'Estado del Servidor',
'WoeHoursLabel' => 'Horarios WOE',
'CastlesLabel' => 'Castillos',
'WhosOnlineLabel' => 'Jugadores Conectados',
'MapStaticsLabel' => 'Estadísticas de Mapas',
'RankingInfoLabel' => 'Ranking',
'VendingInfoLabel' => 'Vending',
'ItemDatabaseLabel' => 'Item DB',
'MobDatabaseLabel' => 'Mob DB',
'JoinUsInFacebookLabel' => '¡Encuéntranos en Facebook!',
'RateUsOnRMSLabel' => '¡Califícanos en RMS!',
// Security
'SecuritySessionInvalid' => 'Lo sentimos, pero tu tiempo de sesión ha expirado. Inténtalo de nuevo.',
'SecurityNeedSession' => 'Lo sentimos, pero no se ha encontrado la sesión del formulario (¿intento de hack?)',
'SecurityNeedToken' => 'Lo sentimos, pero no se ha encontrado la identificación del formulario (¿intento de hack?)',
// Module: account
// - account/changemail
'EmailChangeTitle' => 'Cambiar Correo Electrónico',
'EnterEmailAddress' => 'Introduce una dirección de correo electrónico.',
'EmailCannotBeSame' => 'Tu nuevo correo electrónico no puede ser el mismo que el actual.',
'EmailInvalid' => 'Dirección de correo electrónico no válida.',
'EmailAlreadyRegistered' => "El correo electrónico especificado ya pertenece a otra cuenta.",
'EmailChangeSent' => 'Se ha enviado un correo electrónico a la nueva dirección, con un enlace para confirmar el cambio.',
'EmailAddressChanged' => '¡Se ha cambiado tu dirección de correo electrónico!',
'EmailChangeFailed' => 'No se pudo cambiar la dirección de correo electrónico. Inténtalo de nuevo más tarde.',
'EmailChangeHeading' => 'Cambiar correo electrónico',
'EmailChangeInfo' => 'Si quieres cambiar la dirección de correo electrónico registrada en tu cuenta, debes rellenar el formulario de abajo.',
'EmailChangeInfo2' => 'Tras enviar el formulario, se requerirá que confirmes tu nuevo correo electrónico (se enviará un correo electrónico a la nueva dirección con un enlace).',
'EmailChangeLabel' => 'Dirección de Correo Electrónico nueva',
'EmailChangeInputNote' => '¡Debe ser una dirección de correo electrónico correcta!!',
'EmailChangeButton' => 'Cambiar Dirección de Correo Electrónico',
// - account/changepass
'PasswordChangeTitle' => 'Cambiar Contraseña',
'NeedCurrentPassword' => 'Introduce tu contraseña actual.',
'NeedNewPassword' => 'Introduce tu nueva contraseña.',
'OldPasswordInvalid' => "La contraseña introducida no coincide con la que tenemos guardada.",
'ConfirmNewPassword' => 'Confirma tu nueva contraseña.',
'NewPasswordHasUsername' => 'Tu nueva contraseña no debe contener tu nombre de usuario.',
'NewPasswordInvalid' => 'Tu nueva contraseña contiene caracteres no válidos.',
'NewPasswordSameAsOld' => 'La nueva contraseña no puede ser la misma que tu contraseña actual.',
'NewPasswordNeedUpper' => 'Tu nueva contraseña debe tener al menos %d letra(s) mayúscula(s).',
'NewPasswordNeedLower' => 'Tu nueva contraseña debe tener al menos %d letra(s) minúscula(s).',
'NewPasswordNeedNumber' => 'Tu nueva contraseña debe tener al menos %d número(s).',
'NewPasswordNeedSymbol' => 'Tu nueva contraseña debe tener al menos %d símbolo(s).',
'PasswordHasBeenChanged' => 'Se ha cambiado tu contraseña. Por favor, identifícate de nuevo.',
'FailedToChangePassword' => 'No se pudo cambiar tu contraseña. Por favor, contacta con un administrador.',
'PasswordChangeHeading' => 'Cambiar Contraseña',
'PasswordChangeInfo' => 'Por favor, introduce tu contraseña actual, después la nueva contraseña que desees usar y vuélvela a introducir para confirmarla.',
'CurrentPasswordLabel' => 'Contraseña Actual',
'NewPasswordLabel' => 'Nueva Contraseña',
'NewPasswordConfirmLabel' => 'Vuelve a introducir la Nueva Contraseña',
'PasswordChangeNote' => 'Asegúrate de introducir información correcta.',
'PasswordChangeNote2' => 'Tras cambiar tu contraseña, serás deslogueado.',
'PasswordChangeButton' => 'Cambiar Contraseña',
// - account/changesex
'GenderChangeTitle' => 'Cambiar Sexo',
'GenderChangeBadChars' => 'No puedes cambiar de sexo si alguno de tus personajes es: %s',
'GenderChanged' => 'Se ha cambiado tu sexo. Además, se han descontado %d crédito(s) de tu cuenta.',
'GenderChangedForFree' => 'Se ha cambiado tu sexo.',
'GenderChangeHeading' => 'Cambiar Sexo',
'GenderChangeCost' => 'Los cambios de sexo cuestan %s crédito(s).',
'GenderChangeBalance' => 'Tu balance actual es de %s crédito(s).',
'GenderChangeNoFunds' => 'No tienes suficientes créditos para cambiar de sexo en estos momentos.',
'GenderChangeNoCost' => 'Los cambios de sexo son gratis para ti.',
'GenderChangeCharInfo' => 'No puedes cambiar de sexo si alguno de tus personajes es: %s',
'GenderChangeSubHeading' => '¡Asegúrate de que realmente quieres cambiar de sexo!',
'GenderChangeFormText' => '¿Quieres cambiar tu sexo a %s?',
'GenderChangeConfirm' => '¿Estás totalmente seguro de que quieres cambiar de sexo?',
'GenderChangeButton' => 'Sí, seguro.',
// - account/confirm
'AccountConfirmTitle' => 'Confirma la Cuenta',
'AccountConfirmUnban' => 'Se ha confirmado y activado la cuenta.',
'AccountConfirmMessage' => 'Tu cuenta ha sido confirmada y activada. Ya puedes iniciar sesión.',
// - account/confirmemail
'EmailConfirmTitle' => 'Confirmar Correo Electrónico',
'EmailConfirmFailed' => 'Ha habido un problema técnico al cambiar tu dirección de correo electrónico. Por favor, contacta con un administrador.',
'EmailConfirmChanged' => '¡Se ha cambiado tu dirección de correo electrónico!',
// - account/create
'AccountCreateTitle' => 'Crear Cuenta',
'AccountConfirmBan' => 'Esperando activación de la cuenta: %s',
'AccountCreateEmailSent' => 'Se ha enviado un correo electrónico con detalles para la activación de tu cuenta. Por favor, comprueba tu correo electrónico y activa tu cuenta para iniciar sesión.',
'AccountCreateFailed' => 'Se ha creado tu cuenta, pero no hemos podido enviarte un correo electrónico debido a problemas técnicos. Por favor, contacta con algún miembro del equipo del servidor y pide ayuda.',
'AccountCreated' => 'Enhorabuena, te has registrado con éxito y logueado automáticamente.',
'AccountCreateHeading' => 'Registrarse',
'AccountCreateTerms' => 'Términos del Servicio',
'AccountCreateInfo' => 'Por favor, lee nuestros %s antes de registrar una cuenta para asegurarte de entender las normas respecto a tener una cuenta en nuestro servidor privado de Ragnarok Online.',
'AccountCreateInfo2' => 'Al hacer click en "Crear Cuenta", estarás de acuerdo en aceptar nuestros %s.',
'AccountCreateGenderInfo' => "¡El sexo que escojas aquí afectará al sexo de tus personajes en el juego!",
'AccountServerLabel' => 'Servidor',
'AccountUsernameLabel' => 'Tu Nombre de Usuario',
'AccountPasswordLabel' => 'Tu Contraseña',
'AccountPassConfirmLabel' => 'Confirmar Contraseña',
'AccountEmailLabel' => 'Dirección de Correo Electrónico',
'AccountEmailLabel2' => 'Confirmar dirección de E-mail',
'AccountGenderLabel' => 'Sexo',
'AccountBirthdateLabel' => 'Fecha de Cumpleaños',
'AccountSecurityLabel' => 'Código de Seguridad',
'AccountCreateButton' => 'Crear Cuenta',
'AccountInvalidChars' => "El nombre de usuario solo puede contener los siguientes caracteres: '%s'",
'AccountRecaptchaKey' => 'Necesitas claves de Recaptcha, ver más en config/applications.php (ReCaptchaPublicKey/ReCaptchaPrivateKey)',
'InvalidLoginServer' => 'Has seleccionado un login server no válido. Inténtalo de nuevo seleccionando un servidor válido.',
'InvalidLoginCredentials' => 'Las credenciales introducidas no son válidas. Asegúrate de haber escrito la información correcta e inténtalo de nuevo.',
'UnexpectedLoginError' => 'Ha ocurrido un fallo inesperado. Inténtalo de nuevo o contacta con un administrador.',
'CriticalLoginError' => 'Ha ocurrido algo malo. Contacta con un administrador cuanto antes.',
'UsernameAlreadyTaken' => "El nombre de usuario que has escogido ya ha sido tomado por otro usuario.",
'UsernameTooShort' => sprintf('Tu nombre de usuario debe tener entre %d y %d caracteres.', Flux::config('MinUsernameLength'), Flux::config('MaxUsernameLength')),
'UsernameTooLong' => sprintf('Tu nombre de usuario debe tener entre %d y %d caracteres.', Flux::config('MinUsernameLength'), Flux::config('MaxUsernameLength')),
'PasswordContainsUser' => 'Tu contraseña no puede contener tu nombre de usuario.',
'PasswordHasUsername' => 'Tu contraseña no puede contener tu nombre de usuario.',
'PasswordTooShort' => 'Tu contraseña debe tener entre %d y %d caracteres.',
'PasswordTooLong' => 'Tu contraseña debe tener entre %d y %d caracteres.',
'PasswordsDoNotMatch' => "Las contraseñas no coinciden. Asegúrate de haberlas escrito correctamente.",
'PasswordNeedUpper' => 'Tu contraseña debe contener al menos %d letra(s) mayúscula(s).',
'PasswordNeedLower' => 'Tu contraseña debe contener al menos %d letra(s) minúscula(s).',
'PasswordNeedNumber' => 'Tu contraseña debe contener al menos %d número(s).',
'PasswordNeedSymbol' => 'Tu contraseña debe contener al menos %d símbolo(s).',
'EmailAddressInUse' => "La dirección de correo electrónico que has introducido ya está registrada por otra cuenta. Utiliza otra dirección de correo electrónico.",
'InvalidEmailAddress' => "La dirección de correo electrónico que has introducido no tiene un formato válido.",
'InvalidEmailconf' => "Las direcciones de E-mail no coinciden.",
'InvalidGender' => 'El sexo de la cuenta debe ser "M" o "F"',
'InvalidServer' => "El servidor que has seleccionado no existe.",
'InvalidSecurityCode' => 'Por favor, introduce correctamente el código de seguridad.',
'InvalidPassword' => 'Tu contraseña contiene caracteres no válidos.',
'InvalidBirthdate' => 'Has introducido una fecha de cumpleaños no válida.',
'CriticalRegisterError' => 'Ha ocurrido algo malo. Contacta con un administrador cuanto antes.',
// - account/edit
'AccountEditTitle' => 'Modifcar Cuenta',
'AccountEditTitle2' => 'Modificando Mi Cuenta',
'AccountEditTitle3' => 'Modificando Cuenta (%s)',
'CannotModifyOwnGroupID' => 'No puedes modificar el ID de grupo de tu cuenta.',
'CannotModifyAnyGroupID' => 'No puedes modificar ID de grupo de las cuentas.',
'CannotModifyGroupIDHigh' => 'No puedes asignar a otra cuenta una ID de grupo mayor que el que tienes.',
'InvalidGroupID' => 'ID de grupo no válido.',
'CannotModifyBalance' => 'No puedes modificar los balances de las cuentas.',
'InvalidLastLoginDate' => 'La última fecha de conexión no es válida.',
'AccountModified' => 'Se ha modificado la cuenta.',
'AccountEditHeading' => 'Modificar Cuenta',
'AccountEditButton' => 'Modificar Cuenta',
'AccountEditNotFound' => 'No se ha encontrado la cuenta.',
// - account/index
'AccountIndexTitle' => 'Lista de Cuentas',
'AccountIndexHeading' => 'Cuentas',
'LoginBetweenLabel' => 'Con Actividad Entre',
'BirthdateBetweenLabel' => 'Fecha de Nacimiento Entre',
'AccountIndexNotFound' => 'No se ha encontrado la cuenta.',
// - account/login
'LoginTitle' => 'Iniciar Sesión',
'LoginHeading' => 'Iniciar Sesión',
'LoginButton' => 'Acceder',
'LoginPageMakeAccount' => '¿Aún no tienes una cuenta? ¡Hazte una!',
'TemporarilyBanned' => 'Tu cuenta se encuentra bloqueada temporalmente.',
'PermanentlyBanned' => 'Tu cuenta se encuentra bloqueada permanentemente.',
'IpBanned' => 'La dirección IP desde la que te encuentras tiene el acceso prohibido.',
'PendingConfirmation' => 'Tu cuenta está aún no ha sido activada por correo electrónico.',
// - account/logout
'LogoutTitle' => 'Cerrar Sesión',
'LogoutHeading' => 'Cerrar Sesión',
'LogoutInfo' => 'Has cerrado tu sesión.',
'LogoutInfo2' => 'Por favor, espera un momento mientras te redireccionamos…',
// - account/resend
'ResendTitle' => 'Volver a Enviar Correo Electrónico de Confirmación',
'ResendEnterUsername' => 'Introduce el nombre de usuario de tu cuenta.',
'ResendEnterEmail' => 'Introduce tu dirección de correo electrónico.',
'ResendFailed' => 'No se pudo volver a enviar el código de confirmación.',
'ResendEmailSent' => 'Se ha vuelto a enviar tu código de confirmación. Por favor, comprueba tu correo electrónico para proceder con la activación de tu cuenta.',
'ResendHeading' => 'Volver a Enviar Correo Electrónico de Confirmación',
'ResendInfo' => 'Introduce el nombre de cuenta y la dirección de correo electrónico que has usado durante el registro para poder volver a enviar un correo electrónico de confirmación.',
'ResendServerLabel' => 'Servidor del Registro',
'ResendAccountLabel' => 'Nombre de Usuario de la Cuenta',
'ResendEmailLabel' => 'Dirección de Correo Electrónico',
'ResendServerInfo' => 'Se trata del servidor en el que has registrado la cuenta.',
'ResendAccountInfo' => 'Se trata del nombre de cuenta que has registrado.',
'ResendEmailInfo' => 'Se trata de la dirección de correo electrónico que usaste cuando registraste la cuenta del campo anterior.',
'ResendButton' => 'Volver a Enviar Correo Electrónico de Confirmación',
// - account/resetpass
'ResetPassTitle' => 'Recuperar Contraseña',
'ResetPassEnterAccount' => 'Introduce el nombre de usuario de tu cuenta.',
'ResetPassEnterEmail' => 'Introduce tu dirección de correo electrónico.',
'ResetPassDisallowed' => 'No se puede hacer uso de la recuperación de contraseña para esta cuenta.',
'ResetPassFailed' => 'No se pudo enviar el correo electrónico para recuperar tu contraseña.',
'ResetPassEmailSent' => 'Se ha enviado un correo electrónico con detalles para recuperar tu contraseña.',
'ResetPassTitle' => 'Recuperar Contraseña',
'ResetPassInfo' => 'Si has perdido tu contraseña, puedes recuperarla introduciendo la dirección de correo electrónico que utilizaste para registrar tu cuenta.',
'ResetPassInfo2' => 'Entonces, se enviará un correo electrónico a la dirección especificada con un enlace que te permitirá recuperar tu contraseña, por lo que se requiere una dirección de correo electrónico válida.',
'ResetPassServerLabel' => 'Servidor del Registro',
'ResetPassAccountLabel' => 'Nombre de Usuario de la Cuenta',
'ResetPassEmailLabel' => 'Dirección de Correo Electrónico',
'ResetPassServerInfo' => 'Se trata del servidor en el que has registrado la cuenta.',
'ResetPassAccountInfo' => 'Se trata del nombre de cuenta que has registrado.',
'ResetPassEmailInfo' => 'Se trata de la dirección de correo electrónico que usaste cuando registraste la cuenta del campo anterior.',
'ResetPassButton' => 'Enviar Correo Electrónico de Recuperación de Contraseña',
// - account/resetpw
'ResetPwTitle' => 'Recuperar Contraseña',
'ResetPwFailed' => 'No se pudo recuperar la contraseña. Inténtalo de nuevo más tarde.',
'ResetPwDone' => 'Se ha cambiado tu contraseña y se te ha enviado un correo electrónico con tu nueva contraseña.',
'ResetPwDone2' => 'Se ha cambiado tu contraseña, pero no hemos podido enviar el correo electrónico que contiene tu nueva contraseña. Por favor, repite el proceso de recuperar tu contraseña para resolver este problema.',
// - account/transfer
'TransferTitle' => 'Transferir Créditos de Donación',
'TransferGreaterThanOne' => 'Solo puedes transferir créditos en cantidades mayores que 1.',
'TransferEnterCharName' => 'Debes introducir el nombre del personaje que recibirá los créditos.',
'TransferNoCharExists' => "El personaje '%s' no existe. Asegúrate de haberlo escrito correctamente.",
'TransferNoBalance' => 'No tienes suficiente saldo para realizar la transferencia.',
'TransferUnexpectedError' => 'Ha ocurrido un error inesperado.',
'TransferSuccessful' => '¡Se han transferido los créditos!',
'TransferHeading' => 'Transferir Créditos de Donación',
'TransferSubHeading' => 'Se transferirán los créditos a un personaje del servidor %s.',
'TransferInfo' => 'Tienes %s crédito(s) en estos instantes.',
'TransferInfo2' => 'Introduce la cantidad de créditos que te gustaría transferir y el nombre de un personaje que pertenezca a la cuenta a la que quieras transferir tus créditos.',
'TransferAmountLabel' => 'Cantidad de Créditos',
'TransferCharNameLabel' => 'Nombre de Personaje',
'TransferAmountInfo' => 'Se trata de la cantidad de créditos que deseas transferir.',
'TransferCharNameInfo' => 'Se trata del nombre del personaje que recibirá los créditos enviados.',
'TransferConfirm' => '¿Estás seguro de que quieres hacer la transferencia?',
'TransferButton' => 'Sí, transferir',
'TransferNoCredits' => 'No tienes créditos disponibles en tu cuenta.',
// - account/view
// * account/view submenus
'ModifyAccountLink' => 'Modificar Cuenta',
'AccountViewTitle' => 'Ver Cuenta',
'AccountViewTitle2' => 'Viendo Cuenta (%s)',
'AccountViewTitle3' => 'Viendo Mi Cuenta',
'AccountTempBanFailed' => 'No se pudo bloquear temporalmente la cuenta.',
'AccountPermBanFailed' => 'No se pudo bloquear permanentemente la cuenta.',
'AccountTempBanUnauth' => 'No estás autorizado para bloquear temporalmente a esta cuenta.',
'AccountPermBanUnauth' => 'No estás autorizado para bloquear permanentemente a esta cuenta.',
'AccountLiftTempBan' => 'Se ha desbloqueado el accedo de la cuenta.',
'AccountLiftPermBan' => 'Se ha desbloqueado el acceso de la cuenta.',
'AccountLiftBanUnauth' => "No estás autorizado para desbloquear el acceso de esta cuenta.",
'AccountViewHeading' => 'Viendo Cuenta',
'AccountViewDonateLink' => '(¡Donar!)',
'AccountViewTempBanLabel' => 'Bloquear Temporalmente',
'AccountViewPermBanLabel' => 'Bloquear Permanentemente',
'AccountViewUnbanLabel' => 'Desbloquear acceso',
'AccountBanReasonLabel' => 'Razón:',
'AccountBanUntilLabel' => 'Bloquear Hasta:',
'AccountTempBanButton' => 'Bloquear Cuenta',
'AccountPermBanButton' => 'Bloquear Cuenta Permanentemente',
'AccountTempUnbanButton' => 'Deshacer Bloqueo Temporal',
'AccountPermUnbanButton' => 'Deshacer Bloqueo Permanente',
'AccountBanConfirm' => '¿Estás seguro?',
'AccountBanLogSubHeading' => 'Registro de bloqueos de %s (más recientes primero)',
'BanLogBanTypeLabel' => 'Tipo de Bloqueo',
'BanLogBanDateLabel' => 'Fecha de Bloqueo',
'BanLogBanReasonLabel' => 'Razón del Bloqueo',
'BanLogBannedByLabel' => 'Bloqueado Por',
'BanLogBannedByCP' => 'Panel de Control',
'BanTypeUnbanned' => 'Sin Bloqueo',
'BanTypePermBanned' => 'Bloqueado Permanentemente',
'BanTypeTempBanned' => 'Bloqueado Temporalmente',
'AccountViewCharSubHead' => 'Personajes en %s',
'AccountViewSlotLabel' => 'Slot',
'AccountViewCharLabel' => 'Nombre de Personaje',
'AccountViewClassLabel' => 'Clase',
'AccountViewLvlLabel' => 'Nivel Base',
'AccountViewJlvlLabel' => 'Nivel Job',
'AccountViewZenyLabel' => 'Zeny',
'AccountViewGuildLabel' => 'Guild',
'AccountViewStatusLabel' => 'Estado',
'AccountViewPrefsLabel' => 'Preferencias',
'CharModifyPrefsLink' => 'Modificar Preferencias',
'AccountViewNoChars' => 'Esta cuenta no tiene personajes en %s.',
'AccountViewStorage' => 'Objetos en el Storage de %s',
'AccountViewStorageCount' => '%s tiene %s objeto(s) en su Storage.',
'AccountViewNoStorage' => 'No hay objetos en el Storage de esta cuenta.',
'AccountViewNotFound' => "La cuenta que estás intentando ver no existe.",
// - account/xferlog
'XferLogTitle' => 'Historial de Transferencia de Crédito',
'XferLogHeading' => 'Historial de Transferencia de Crédito',
'XferLogReceivedSubHead' => 'Transferencias: Recibidas',
'XferLogSentSubHead' => 'Transferencias: Enviadas',
'XferLogCreditsLabel' => 'Créditos',
'XferLogFromLabel' => 'Correo Electrónico Remitente',
'XferLogDateLabel' => 'Fecha de Transferencia',
'XferLogCharNameLabel' => 'Para el Personaje',
'XferLogNotReceived' => 'No has recibido ninguna transferencia de crédito.',
'XferLogNotSent' => 'No has hecho ninguna transferencia de crédito.',
// Module: character
// - character/changeslot
// - character/index
// - character/mapstats
// - character/online
// - character/prefs
// - character/resetlook
'CantResetLookWhenOnline' => 'No se puede restaurar la apariencia de %s mientras está conectado.',
'ResetLookSuccessful' => "¡Se ha restaurado la apariencia de %s!",
'ResetLookFailed' => "No se pudo restaurar la apariencia de %s.",
// - character/resetpos
'CantResetPosWhenOnline' => 'No se puede restaurar la posición de %s mientras está conectado.',
'CantResetFromCurrentMap' => "No puedes restaurar la posición de %s en su mapa actual.",
'ResetPositionSuccessful' => "¡Se ha restaurado la posición de %s!",
'ResetPositionFailed' => "No se pudo restaurar la posición de %s.",
// - character/view
// - character/divorce
'DivorceTitle' => 'Divorciar',
'DivorceHeading' => 'Divorciar',
'DivorceNotMarried' => '%s no se encuentra casado.',
'DivorceInvalidPartner' => 'El ID del cónyuge no es válido.',
'DivorceInvalidChild' => 'El ID del hijo(a) no es válido.',
'DivorceMustBeOffline' => 'Tanto %s como el cónyuge deben estar desconectados.',
'DivorceMustBeOffline2' => '%s, su cónyuge y su hijo deben estar desconectados.',
'DivorceText1' => "¿Estás seguro de que deseas el divorcio entre %s y su cónyuge?",
'DivorceText2' => 'Si %s tiene un hijo, éste se volverá huérfano.',
'DivorceText3' => 'Además, sus Anillos de matrimonio serán eliminados.',
'DivorceButton' => 'Sí, hazlo por favor.',
'DivorceSuccessful' => '¡Se ha divorciado a %s!',
// Module: cplog
// - cplog/index.php
// - cplog/login.php
// - cplog/paypal.php
// - cplog/resetpass.php
// - cplog/txnview.php
// Module: donate
// - donate/complete
// - donate/history
// - donate/index
// - donate/trusted
// Module: errors
// - errors/missing_action
'MissingActionTitle' => 'Acción Faltante', // Nociones de Programación Orientada a Objetos: Acción = "Página"
'MissingActionHeading' => 'Acción Faltante!', // Una acción es un conjunto de Vistas, cada elemento de la acción
'MissingActionModLabel' => 'Módulo:',
'MissingActionActLabel' => 'Acción:',
'MissingActionReqLabel' => 'URI de petición:',
'MissingActionLocLabel' => 'Localización en el sistema de archivos:',
// - errors/missing_view
'MissingViewTitle' => 'Vista Faltante',
'MissingViewHeading' => 'Vista Faltante!',
'MissingViewModLabel' => 'Módulo:',
'MissingViewActLabel' => 'Acción:',
'MissingViewReqLabel' => 'URI de petición:',
'MissingViewLocLabel' => 'Localización en el sistema de archivos:',
// Module: guild
// - guild/export
// - guild/index
// - guild/view
// Module: history
// - history/cplogin
'HistoryCpLoginTitle' => 'Accesos al Panel de Control',
'HistoryCpLoginHeading' => 'Accesos al Panel de Control',
'HistoryLoginDateLabel' => 'Fecha/Hora de Acceso',
'HistoryIpAddrLabel' => 'Dirección IP',
'HistoryErrorCodeLabel' => 'Código de Error',
'HistoryNoCpLogins' => 'No se ha encontrado ningún intento de acceso al panel de control.',
// -history/emailchange
'HistoryEmailTitle' => 'Cambios de Correo Electrónico',
'HistoryEmailHeading' => 'Cambios de Correo Electrónico',
'HistoryEmailRequestDate' => 'Fecha/Hora de la petición',
'HistoryEmailRequestIp' => 'IP de la petición',
'HistoryEmailOldAddress' => 'Correo Electrónico Antiguo',
'HistoryEmailNewAddress' => 'Correo Electrónico Nuevo',
'HistoryEmailChangeDate' => 'Fecha del Cambio',
'HistoryEmailChangeIp' => 'IP del Cambio',
'HistoryNoEmailChanges' => 'No se ha encontrado ningún intento de cambio de correo electrónico.',
// - history/gamelogin
'HistoryGameLoginTitle' => 'Conexiones en el Juego',
'HistoryGameLoginHeading' => 'Conexiones en el Juego',
'HistoryRepsCodeLabel' => 'Código de Respuesta',
'HistoryLogMessageLabel' => 'Mensaje en el Registro',
'HistoryNoGameLogins' => 'No se ha encontrado ningún intento de conexión en el juego.',
// - history/index
'HistoryIndexTitle' => 'Historial de Mi Cuenta',
'HistoryIndexHeading' => 'Historial de Mi Cuenta',
'HistoryIndexInfo' => 'Aquí puedes ver la actividad de tu cuenta en el pasado.',
'HistoryIndexInfo2' => 'Por favor, selecciona una acción en el menú.',
// - history/passchange
'HistoryPassChangeTitle' => 'Cambios de Contraseña',
'HistoryPassChangeHeading' => 'Cambios de Contraseña',
'HistoryPassChangeChangeDate' => 'Fecha del Cambio',
'HistoryPassChangeChangeIp' => 'IP del Cambio',
'HistoryNoPassChanges' => 'No se ha encontrado ningún cambio de contraseña.',
// -history/passreset
'HistoryPassResetTitle' => 'Recuperaciones de Contraseña',
'HistoryPassResetHeading' => 'Recuperaciones de Contraseña',
'HistoryPassResetRequestDate' => 'Fecha/Hora de la Petición',
'HistoryPassResetRequestIp' => 'IP de la Petición',
'HistoryPassResetResetDate' => 'Fecha de la Recuperación',
'HistoryPassResetResetIp' => 'IP de la Recuperación',
'HistoryNoPassResets' => 'No se ha encontrado ningún intento de recuperar la contraseña.',
// Module: ipban
// - ipban/add
'IpbanAddTitle' => 'Añadir Bloqueos de IP',
'IpbanEnterIpPattern' => 'Introduce una dirección o un rango de IP.',
'IpbanInvalidPattern' => 'La dirección o el rango de IP introducido no es válido/a.',
'IpbanWhitelistedPattern' => 'Este rango de IP está en la lista blanca, por lo que no puede ser bloqueado.',
'IpbanEnterReason' => 'Introduce una razón para el bloqueo.',
'IpbanSelectUnbanDate' => 'Se requiere una fecha de desbloqueo.',
'IpbanFutureDate' => 'La fecha de readmisión debe ser una fecha futura.',
'IpbanAlreadyBanned' => 'La IP (%s) ya está bloqueada.',
'IpbanPatternBanned' => "Se ha bloqueado la dirección o rango de IP '%s'.",
'IpbanAddFailed' => 'No se pudo añadir este bloqueo de IP.',
'IpbanAddHeading' => 'Añadir Bloqueos de IP',
'IpbanIpAddressLabel' => 'Dirección IP',
'IpbanReasonLabel' => 'Razón del Bloqueo',
'IpbanUnbanDateLabel' => 'Fecha de Desbloqueo',
'IpbanIpAddressInfo' => 'Puedes especificar un rango de IP, como por ejemplo 218.139.*.*',
'IpbanAddButton' => 'Añadir Bloqueos de IP',
// - ipban/edit
'IpbanEditTitle' => 'Modificar Bloqueos de IP',
'IpbanEnterEditReason' => 'Introduce una razón para la modificación del bloqueo de IP.',
'IpbanEditFailed' => 'No se pudo modificar el bloqueo de IP.',
'IpbanEditHeading' => 'Modifcar Bloqueos de IP',
'IpbanEditReasonLabel' => 'Razón de la Edición',
'IpbanEditButton' => 'Modifcar Bloqueo de IP',
// - ipban/index
'IpbanListTitle' => 'Lista de Bloqueos de IP',
'IpbanListHeading' => 'Lista de Bloqueos de IP',
'IpbanBannedIpLabel' => 'IP Bloqueada',
'IpbanBanDateLabel' => 'Fecha de Bloqueo',
'IpbanBanReasonLabel' => 'Razón de Bloqueo',
'IpbanBanExpireLabel' => 'Fecha de Expiración',
'IpbanModifyLink' => 'Modificar',
'IpbanRemoveLink' => 'Eliminar',
'IpbanUnbanButton' => 'Desbloquear Seleccionados',
'IpbanListNoBans' => 'En estos momentos no hay ningún bloqueo de IP.',
// - ipban/remove
'IpbanRemoveTitle' => 'Deshacer Bloqueo de IP',
'IpbanEnterRemoveReason' => 'Introduce una razón para desbloquear esta IP o rango.',
'IpbanNotBanned' => 'La IP (%s) no está bloqueada.',
'IpbanPatternUnbanned' => "Se ha desbloqueado la IP o rango '%s'.",
'IpbanRemoveFailed' => 'No se pudo deshacer el bloqueo de IP.',
'IpbanRemoveHeading' => 'Deshacer Bloqueo de IP',
'IpbanRemoveReasonLabel' => 'Razón de Desbloqueo',
'IpbanRemoveButton' => 'Desbloquear IP o rango',
// - ipban/unban
'IpbanNothingToUnban' => 'No hay nada que se pueda desbloquear.',
'IpbanEnterUnbanReason' => 'Introduce un motivo para desbloquear esta(s) IP.',
'IpbanUnbanned' => '¡Se ha(n) desbloqueado la(s) IP seleccionada(s)!',
'IpbanUnbanFailed' => '¡No se pudo/pudieron desbloquear %d IP especificada(s)!',
// Module: item
// - item/add
// - item/copy
// - item/edit
// - item/index
// - item/view
// Module: itemshop
// - itemshop/add
// - itemshop/delete
// - itemshop/edit
// - itemshop/imagedel
// Module: logdata
// - logdata/chat
// - logdata/command
'CommandLogTitle' => 'Lista de Comandos',
'CommandLogHeading' => 'Comandos Registrados',
'CommandLogNotFound' => 'No se ha registrado ningún comandos.',
'CommandLogDateLabel' => 'Fecha/Hora',
'CommandLogAccountIdLabel'=> 'ID de Cuenta',
'CommandLogCharIdLabel' => 'ID de Personaje',
'CommandLogCharNameLabel' => 'Nombre del Personaje',
'CommandLogCommandLabel' => 'Comando',
'CommandLogMapLabel' => 'Mapa',
// - logdata/index
// - logdata/login
// - logdata/pick
'PickLogTitle' => 'Lista de Transacciones de Objetos',
'PickLogHeading' => 'Transacciones Registradas de Objetos',
'PickLogNotFound' => 'No se ha registrado ninguna transacción de objetos.',
'PickLogDateLabel' => 'Fecha/Hora',
'PickLogCharacterLabel' => 'Personaje',
'PickLogTypeLabel' => 'Tipo',
'PickLogItemLabel' => 'Nombre de Objeto',
'PickLogAmountLabel' => 'Cantidad',
'PickLogRefineLabel' => 'Refinado',
'PickLogCard0Label' => 'Carta 1',
'PickLogCard1Label' => 'Carta 2',
'PickLogCard2Label' => 'Carta 3',
'PickLogCard3Label' => 'Carta 4',
'PickLogMapLabel' => 'Mapa',
// - logdata/zeny
'ZenyLogTitle' => 'Lista de Transacciones de Zeny',
'ZenyLogHeading' => 'Transacciones Registradas de Zeny',
'ZenyLogNotFound' => 'No se ha registrado ninguna transacción de zeny.',
'ZenyLogDateLabel' => 'Fecha/Hora',
'ZenyLogCharacterLabel' => 'Personaje',
'ZenyLogSourceLabel' => 'ID de Personaje', // No estoy seguro, tomado de la traducción portuguesa de JulioCF
'ZenyLogTypeLabel' => 'Tipo',
'ZenyLogAmountLabel' => 'Cantidad',
'ZenyLogMapLabel' => 'Mapa',
// Module: mail
// - mail/index
'MailerTitle' => 'Enviar Correo Electrónico',
'MailerHeading' => 'Enviar Correo Electrónico',
'MailerEnterToAddress' => 'Introduce el destinatario.',
'MailerEnterSubject' => 'Introduce el asunto.',
'MailerEnterBodyText' => 'Introduce el cuerpo del mensaje.',
'MailerEmailHasBeenSent' => 'Se ha enviado satisfactoriamente tu correo electrónico a %s.',
'MailerFailedToSend' => 'El sistema de envío de correo electrónico no pudo enviar este mensaje. Es posible que esto ocurra debido a un fallo en la configuración.',
'MailerInfo' => 'Utiliza el formulario de abajo para enviar un correo electrónico mediante el panel de control.',
'MailerFromLabel' => 'De',
'MailerToLabel' => 'Para',
'MailerSubjectLabel' => 'Asunto',
'MailerBodyLabel' => 'Cuerpo del mensaje',
// Module: main
// - main/index
'MainPageHeading' => 'Panel de Control Flux',
'MainPageInfo' => "Si puedes ver esta página, significa que has instalado Flux correctamente.",
'MainPageInfo2' => "¿Quieres cambiar esta página? Muy bien, puedes hacerlo así:",
'MainPageStep1' => 'Abre "%s" en un editor de texto.',
'MainPageStep2' => 'Edita el archivo desde el propio editor y guarda los cambios.',
'MainPageThanks' => '¡Gracias por usar Flux!',
// - main/pagenotfound
'PageNotFoundTitle' => 'Error 404: Página No Encontrada. :(',
'PageNotFoundHeading' => 'Página No Encontrada',
'PageNotFoundInfo' => 'No se ha encontrado la página que buscas en nuestro servidor. Verifica que la dirección es correcta e inténtalo de nuevo.',
// - main/preprocess
'DisallowedDuringWoE' => 'No se puede acceder a la página que buscas en horario de WoE.',
// Module: monster
// - monster/index
// - monster/view
// Module: purchase
// - purchase/add
// - purchase/cart
// - purchase/checkout
// - purchase/clear
// - purchase/index
// - purchase/pending
// - purchase/remove
// Module: ranking
// - ranking/character
// - ranking/guild
// - ranking/zeny
// Module: server
// - server/info
'ServerInfoTitle' => 'Información del Servidor',
'ServerInfoHeading' => 'Información del Servidor',
'ServerInfoText' => "Aquí puedes encontrar informaciones varias sobre el servidor.",
'ServerInfoSubHeading' => 'Información de %s',
'ServerInfoSubHeading2' => 'Información de Clases en %s',
'ServerInfoAccountLabel' => 'Cuentas',
'ServerInfoCharLabel' => 'Personajes',
'ServerInfoGuildLabel' => 'Guilds',
'ServerInfoPartyLabel' => 'Parties',
'ServerInfoZenyLabel' => 'Zeny',
// - server/status
'ServerStatusTitle' => 'Estado Actual del Servidor',
'ServerStatusHeading' => 'Estado del Servidor',
'ServerStatusInfo' => "Entender el estado online u offline de cada servidor puede servirte de ayuda para entender cómo un fallo del servidor puede relacionarse con un problema que puedas tener. Por ejemplo, si el login server aparece offline, significa que no podrás conectarte al juego. El character server y el/los map server(s) son necesarios para el juego una vez te has conectado e identificado.",
'ServerStatusServerLabel' => 'Servidor',
'ServerStatusLoginLabel' => 'Login Server',
'ServerStatusCharLabel' => 'Character Server',
'ServerStatusMapLabel' => 'Map Server',
'ServerStatusOnlineLabel' => 'Jugadores Conectados',
// Module: service
// - service/tos
'TermsTitle' => 'Términos del Servicio',
'TermsHeading' => 'Términos del Servicio',
'TermsInfo' => '¡Por favor, léelos atentamente antes de crearte una cuenta!',
'TermsInfo2' => "PARA EL ADMINISTRADOR DEL PANEL DE CONTROL: Puedes añadir los Términos del Servicio de tu servidor directamente en este archivo. Su localización es: %s",
// Module: unauthorized
// - unauthorized/index
'UnauthorizedTitle' => 'No estás autorizado',
'UnauthorizedHeading' => 'No estás autorizado',
'UnauthorizedInfo' => 'No estás autorizado para ver esta página. Redireccionando…',
// Module: woe
// - woe/index
'WoeTitle' => 'Horario de WoE',
'WoeHeading' => 'Horario de War of Emperium',
'WoeInfo' => "Abajo se encuentra el horario de WoE en %s. Este horario está sujeto a cambios en cualquier momento, pero esperemos que no sea así.",
'WoeServerTimeInfo' => 'La hora actual del servidor es:',
'WoeServerLabel' => 'Servidores',
'WoeTimesLabel' => 'Horarios de War of Emperium',
'WoeNotScheduledInfo' => 'No se ha asignado ningún horario para WoE.',
// Module: webcommands
'WCTitleLabel' => 'Web Commands',
'InvalidVIPTime' => 'Invalid VIP Time.',
'VIPTimeDateLabel' => 'VIP Until',
'VIPStateLabel' => 'VIP Status',
'CashLogTitle' => 'List CashPoints Log',
'CashLogHeading' => 'CashPoint Log',
'CashLogNotFound' => 'No cash logs found.',
'CashLogDateLabel' => 'Date/Time',
'CashLogCharacterLabel' => 'Character',
'CashLogTypeLabel' => 'Type',
'CashLogCashTypeLabel' => 'Cash Type',
'CashLogAmountLabel' => 'Amount',
'CashLogMapLabel' => 'Map',
'BranchLogTitle' => 'List Branch Log',
'BranchLogHeading' => 'Branch Log',
'BranchLogNotFound' => 'No branch logs found.',
'BranchLogIDLabel' => 'Branch Log ID',
'BranchLogDateLabel' => 'Date / Time',
'BranchLogAccountIDLabel' => 'Account ID',
'BranchLogCharIDLabel' => 'Char ID',
'BranchLogCharNameLabel' => 'Char Name',
'BranchLogMapLabel' => 'Map',
'CharLogTitle' => 'List Character Log',
'CharLogHeading' => 'Character Log',
'CharLogNotFound' => 'No character logs found.',
'CharLogDateLabel' => 'Date / Time',
'CharLogMsgLabel' => 'Action',
'CharLogAccountIDLabel' => 'Account ID',
'CharLogCharNumLabel' => 'Character slot',
'CharLogCharNameLabel' => 'Character Name',
'InterLogTitle' => 'List of Interactions Log',
'InterLogHeading' => 'Interactions Log',
'InterLogNotFound' => 'No Interactions logs found.',
'InterLogDateLabel' => 'Date / Time',
'InterLogLabel' => 'Interactions Log',
'MVPLogTitle' => 'List MVP Log',
'MVPLogHeading' => 'MVP Log',
'MVPLogNotFound' => 'No MVP logs found.',
'MVPLogIDLabel' => 'MVP Log ID',
'MVPLogDateLabel' => 'Date / Time',
'MVPLogCharacterLabel' => 'Character ID',
'MVPLogMonsterLabel' => 'MVP Monster',
'MVPLogPrizeLabel' => 'MVP Prize',
'MVPLogExpLabel' => 'MVP Experience',
'MVPLogMapLabel' => 'Map',
'NPCLogTitle' => 'List NPC Log',
'NPCLogHeading' => 'NPC Log',
'NPCLogNotFound' => 'No npc logs found.',
'NPCLogIDLabel' => 'NPC ID',
'NPCLogDateLabel' => 'Date / Time',
'NPCLogAccountIDLabel' => 'Account ID',
'NPCLogCharIDLabel' => 'Character ID',
'NPCLogCharNameLabel' => 'Character Name',
'NPCLogMapLabel' => 'Map',
'NPCLogMsgLabel' => 'Message',
'MailerSelectTemplateLabel' => 'Select Template',
'MainPageWelcome' => 'Welcome to %s!',
'ServerStatusPeakLabel' => 'Player Peak',
'CFTitleSubmit' => 'Contact Us',
'CMSNewsHeader' => 'Announcements',
'CMSPageHeader' => 'Content Management System',
'CMSPageText' => 'This module enables server admins and staff to create pages within their website with no prior flux/coding knowledge. The built-in news system can also be swapped out for an rss feed by modifying the application settings.',
'CMSNewsTitleError' => 'News title is required!',
'CMSNewsBodyError' => 'News body is required!',
'CMSPageTitleError' => 'Page Title is required!',
'CMSPageBodyError' => 'Page body is required!',
'CMSPagePathError' => 'Page path is required!',
'CMSNewsAdded' => 'News added to system',
'CMSPagesAdded' => 'Your new page has been added',
'CMSNewsUpdated' => 'News updated',
'CMSPageUpdated' => 'Your page has been updated',
'CMSNewsAddTitle' => 'Add a news item',
'CMSPageAddTitle' => 'Add a new page',
'CMSNewsEditTitle' => 'Edit news',
'CMSPageEditTitle' => 'Edit page',
'CMSNewsNotFound' => 'News not found!',
'CMSPageNotFound' => 'Page not found!',
'CMSNewsDeleted' => 'News deleted',
'CMSPageDeleted' => 'Your page has been deleted',
'CMSNewsEmpty' => 'No news articles have been found. Are you using the correct News Type? (CMSNewsType setting)',
'CMSNewsRSSNotFound' => "RSS feed can't be found. Make sure the CMSNewsRSS setting is correct, or switch CMSNewsType to 1 to use built-in news system!",
'CMSPageEmpty' => 'No page added',
'CMSNewsLink' => 'read more...',
'CMSEdit' => 'Edit',
'CMSDelete' => 'Delete',
'CMSNewsTitleLabel' => 'News Title',
'CMSNewsBodyLabel' => 'News Body',
'CMSNewsLinkLabel' => 'News Link',
'CMSNewsAuthorLabel' => 'News Author',
'CMSPageTitleLabel' => 'Page Title',
'CMSPageBodyLabel' => 'Page Body',
'CMSPagePathLabel' => 'Page Path',
'CMSCreatedLabel' => 'Date Created',
'CMSModifiedLabel' => 'Date Modified',
'CMSActionLabel' => 'Action',
'CMSConfirmDeleteLabel' => 'Are you sure you want to delete?',
'CMSPageCreate' => 'Create now?',
'CMSOptionalLabel' => '(Optional)',
'CMSRequiredLabel' => '(Required)',
'CMSCreateLabel' => 'Add News',
'TLHeaderTasks' => 'Tasks',
'TLHeaderOwner' => 'Owner',
'TLHeaderPriority' => 'Priority',
'TLHeaderStatus' => 'Status',
'TLHeaderCreated' => 'Created',
'TLHeaderModified' => 'Modified',
'TLHeaderResources' => 'Additional Resources',
'TLHeaderBody' => 'Body',
'SDHeader' => 'Service Desk',
'SDCreateNew' => 'Create a new ticket',
'SDWelcomeText' => 'Welcome to the Service Desk',
'SDNoTickets' => 'You have not created any tickets.',
'SDNoBlankResponse' => 'For a response to register, you must type something into the box!',
'SDNoCatsAvailable' => 'No Categories Available',
'SDNoOpenTickets' => 'There are no Open tickets in the database.',
'SDNoInactiveTickets' => 'You have no in-active tickets at the moment.',
'SDNoClosedTickets' => 'There are no Closed tickets in the database.',
'SDNoCats' => 'There are no categories within the database.',
'SDHuh' => 'You should not be here o.O',
'SDPointerChatLog' => 'We recommend pasting to pastebin.com then putting the link in here.',
'SDPointerScreenShot' => 'Provide image links as evidence',
'SDPointerVideoLink' => 'We recommend uploading to youtube then putting the link in here.',
'SDHeaderID' => 'Ticket #',
'SDHeaderSubject' => 'Subject',
'SDHeaderCategory' => 'Category',
'SDHeaderStatus' => 'Current Status',
'SDHeaderLastAuthor' => 'Last Author',
'SDHeaderTimestamp' => 'Created',
'SDHeaderAccount' => 'Account',
'SDHeaderTeam' => 'Team',
'SDH3ActiveTickets' => 'Active Tickets',
'SDH3InActiveTickets' => 'In-Active Tickets',
'SDH3ClosedTickets' => 'Closed Tickets',
'SDH3CurrentCat' => 'Current Categories',
'SDH3CreateCat' => 'Create New Category',
'SDH3StaffList' => 'Current Staff Settings',
'SDH3StaffCreate' => 'Add Staff Settings',
'SDReOpenPlayer' => 'Ticket Re-Opened by player',
'SDReOpenStaff' => '',
'SDRespTable1' => 'Respond and Return to Ticket',
'SDRespTable2' => 'Respond and Return to List',
'SDRespTable3' => 'Respond and Resolve Ticket',
'SDRespTable4' => 'Respond and Escalate',
'SDRespTable5' => 'Close Ticket',
'SDRespTable6' => 'Respond and Re-Open Ticket',
'SDRespTable7' => 'Resolve Ticket and Credit Account',
'SDGroup1' => 'Support Staff',
'SDGroup2' => 'Head of Support',
'SDGroup3' => 'Administration',
'SDLinkOpenNew' => 'Open a new ticket',
);
?>
================================================
FILE: lang/id_id.php
================================================
return array(
// Generic/Misc.
'Language' => 'Indonesian',
'YesLabel' => 'Ya',
'NoLabel' => 'Tidak',
'NoteLabel' => 'Catatan',
'GenderTypeMale' => 'Pria',
'GenderTypeFemale' => 'Wanita',
'GenderTypeServer' => 'Server',
'RefreshSecurityCode' => 'Muat Ulang Kode Keamanan',
'NoneLabel' => 'Kosong',
'NeverLabel' => 'Jangan Pernah',
'NotApplicableLabel' => 'Tidak Dapat Digunakan',
'UnknownLabel' => 'Tidak Diketahui',
'IsEqualToLabel' => 'sama dengan',
'IsGreaterThanLabel' => 'lebih besar dari',
'IsLessThanLabel' => 'lebih kecil dari',
'AllLabel' => 'Semua',
'SearchLabel' => 'Cari…',
'GoBackLabel' => 'Kembali ke halaman sebelumnya…',
'SearchButton' => 'Cari',
'ResetButton' => 'Reset',
'FilterButton' => 'Penyaringan',
'NotAcceptingDonations' => "Kami mohon maaf, tapi saat ini kami sedang tidak menerima donasi. Maaf untuk ketidaknyamanannya.",
//'NotAcceptingDonations' => "Kami mohon maaf, tapi sistem donasi kami saat ini sedang dalam perbaikan, mohon ulangi beberapa saat lagi.",
'FoundSearchResults' => 'Ditemukan. Total %d dari %d halaman. Menampilkan hasil %d-%d.',
'LoginToDonate' => 'Harap login untuk melakukan donasi.',
'UnknownCharacter' => 'Karakter tidak ditemukan.',
'AccountIdLabel' => 'ID Akun',
'AccountGroupIDLabel' => 'ID Grup',
'AccountStateLabel' => 'State',
'CreditBalanceLabel' => 'Saldo Kredit',
'UsernameLabel' => 'Nama Pengguna',
'PasswordLabel' => 'Sandi',
'EmailAddressLabel' => 'E-mail',
'GenderLabel' => 'Jenis Kelamin',
'LoginCountLabel' => 'Jumlah Login', //bukan "Jumlah". xD jumlah = sum(), itu count() :P
'LastLoginDateLabel' => 'Terakhir Login',
'LastUsedIpLabel' => 'IP Terakhir',
'AccountStateNormal' => 'Normal',
'AccountStatePending' => 'Pending',
'AccountStatePermBanned' => 'Ban Permanen',
'AccountStateTempBanLbl' => 'Ban Sementara',
'AccountStateTempBanned' => 'Ban Sementara (unban: %s)',
'OnlineLabel' => 'Online',
'OfflineLabel' => 'Offline',
'ItemIdLabel' => 'ID Barang',
'ItemNameLabel' => 'Nama Barang',
'ItemAmountLabel' => 'Jumlah',
'ItemIdentifyLabel' => 'Teridentifikasi',
'ItemRefineLabel' => 'Tempa',
'ItemBrokenLabel' => 'Rusak',
'ItemCard0Label' => 'Kartu 1',
'ItemCard1Label' => 'Kartu 2',
'ItemCard2Label' => 'Kartu 3',
'ItemCard3Label' => 'Kartu 4',
//SIDEBAR
//FluxCP Menu Items
//Categories
'MainMenuLabel' => 'Menu Utama',
'ForumLabel' => 'Forum',
'AccountLabel' => 'Akun',
'CharacterLabel' => 'Character',
'ServiceDeskLabel' => 'Service Desk',
'CPLogsLabel' => 'CP Logs',
'FluxAdminLabel' => 'Flux Admin',
'PagesLabel' => 'Pages',
'IPBanListLabel' => 'IP Ban List',
'GuildsLabel' => 'Guilds',
'rALogsLabel' => 'rA Logs',
'SendMailLabel' => 'Send Mail',
'ReInstallLabel' => 'Re-Install',
'TaskListLabel' => 'Task List',
'DonationsLabel' => 'Donasi',
'InformationLabel' => 'Informasi',
'DatabaseLabel' => 'Database',
'SocialLabel' => 'Social',
//SubMenus
'HomeLabel' => 'Beranda',
'NewsLabel' => 'Berita',
'DownloadsLabel' => 'Download',
'RulesLabel' => 'Peraturan',
'ContactUsLabel' => 'Hubungi Kami',
'MyAccountLabel' => 'Akun Saya',
'HistoryLabel' => 'History',
'ServiceDeskLabel' => 'Bantuan',
'PurchaseLabel' => 'Pembelian',
'DonateLabel' => 'Berikan Donasi',
'ServerInfoLabel' => 'Info Server',
'ServerStatusLabel' => 'Status Server',
'WoeHoursLabel' => 'Jadwal WOE',
'CastlesLabel' => 'Kastil',
'WhosOnlineLabel' => 'Pemain Online',
'MapStaticsLabel' => 'Informasi Map',
'RankingInfoLabel' => 'Informasi Peringkat',
'VendingInfoLabel' => 'Informasi Vending',
'ItemDatabaseLabel' => 'Database Barang',
'MobDatabaseLabel' => 'Database Monster',
'JoinUsInFacebookLabel' => 'Kunjungi kami di Facebook!',
'RateUsOnRMSLabel' => 'Kunjungi kami di RMS!',
// Module: account
// - account/changemail
'EmailChangeTitle' => 'Ubah E-mail',
'EnterEmailAddress' => 'Masukkan alamat e-mail.',
'EmailCannotBeSame' => 'E-mail baru Anda tidak boleh sama dengan yang ada saat ini.',
'EmailInvalid' => 'Alamat e-mail tidak diizinkan.',
'EmailAlreadyRegistered' => 'Alamat e-mail yang Anda masukkan sudah digunakan untuk akun lain',
'EmailChangeSent' => 'Sebuah e-mail telah dikirim ke alamat e-mail Anda yang baru dengan tautan yang akan mengkonfirmasi penggantian.',
'EmailAddressChanged' => 'Alamat e-mail Anda telah diubah!',
'EmailChangeFailed' => 'Gagal untuk mengubah alamat e-mail. Harap ulangi beberapa saat lagi.',
'EmailChangeHeading' => 'Ubah E-mail',
'EmailChangeInfo' => 'Jika Anda ingin mengubah alamat e-mail, Anda dapat mengisi formulir di bawah ini.',
'EmailChangeInfo2' => 'Setelah mengirim formulir, Anda akan diminta untuk mengkonfimasi alamat e-mail baru Anda (sebuah e-mail akan dikirimkan ke alamat e-mail baru berisi sebuah tautan).',
'EmailChangeLabel' => 'Alamat e-mail baru',
'EmailChangeInputNote' => 'Harus alamat e-mail yang benar!',
'EmailChangeButton' => 'Ubah alamat e-mail',
// - account/changepass
'PasswordChangeTitle' => 'Ubah Sandi',
'NeedCurrentPassword' => 'Masukkan sandi yang digunakan.',
'NeedNewPassword' => 'Masukkan sandi baru.',
'OldPasswordInvalid' => 'Kata sandi yang Anda berikan tidak sesuai.',
'ConfirmNewPassword' => 'Konfirmasi kata sandi baru Anda.',
'NewPasswordHasUsername' => 'Tidak boleh ada nama pengguna di kata sandi yang baru.',
'NewPasswordInvalid' => 'Terdapat karakter yang tidak diizinkan di kata sandi yang baru.',
'NewPasswordSameAsOld' => 'Kata sandi yang baru tidak boleh sama dengan kata sandi yang ada saat ini.',
'NewPasswordNeedUpper' => 'Kata sandi yang baru harus menggunakan minimal %d huruf kapital.',
'NewPasswordNeedLower' => 'Kata sandi yang baru harus menggunakan minimal %d huruf kecil.',
'NewPasswordNeedNumber' => 'Kata sandi yang baru harus menggunakan minimal %d angka.',
'NewPasswordNeedSymbol' => 'Kata sandi yang baru harus menggunakan minimal %d simbol.',
'PasswordHasBeenChanged' => 'Kata sandi Anda telah diubah, silakan login kembali.',
'FailedToChangePassword' => 'Penggantian kata sandi tidak berhasil. Harap menghubungi admin.',
'PasswordChangeHeading' => 'Ubah Kata Sandi',
'PasswordChangeInfo' => 'Silakan masukkan kata sandi Anda, lalu masukkan kata sandi yang baru.',
'CurrentPasswordLabel' => 'Kata Sandi Saat Ini',
'NewPasswordLabel' => 'Kata Sandi Baru',
'NewPasswordConfirmLabel' => 'Konfirmasi Kata Sandi Baru',
'PasswordChangeNote' => 'Harap gunakan informasi yang benar.',
'PasswordChangeNote2' => 'Setelah mengganti kata sandi, Anda akan logout.',
'PasswordChangeButton' => 'Ganti Kata Sandi',
// - account/changesex
'GenderChangeTitle' => 'Ubah Jenis Kelamin', //swt -_-
'GenderChangeBadChars' => 'Anda tidak dapat mengubah jenis kelamin jika ada karakter yang Anda miliki: %s',
'GenderChanged' => 'Jenis kelamin Anda telah diganti dan %d kredit telah dikurangi dari akun Anda.',
'GenderChangedForFree' => 'Jenis kelamin Anda telah diganti.',
'GenderChangeHeading' => 'Ubah Jenis Kelamin',
'GenderChangeCost' => 'Biaya penggantian jenis kelamin = %s kredit.',
'GenderChangeBalance' => 'Kredit yang Anda miliki = %s .',
'GenderChangeNoFunds' => 'Anda tidak memiliki kredit yang cukup untuk mengganti jenis kelamin.',
'GenderChangeNoCost' => 'Untuk Anda, gratis untuk mengganti jenis kelamin.',
'GenderChangeCharInfo' => 'Anda tidak dapat mengganti jenis kelamin jika Anda memiliki karakter dengan job: %s',
'GenderChangeSubHeading' => 'Yakinkan jika Anda ingin benar-benar mengganti!',
'GenderChangeFormText' => 'Akankah Anda mengganti jenis kelamin menjadi %s?',
'GenderChangeConfirm' => 'Apa Anda yakin untuk mengganti jenis kelamin Anda?',
'GenderChangeButton' => 'Ya.',
// - account/confirm
'AccountConfirmTitle' => 'Konfirmasi akun',
'AccountConfirmUnban' => 'Akun telah dikonfirmasi dan diaktifkan.',
'AccountConfirmMessage' => 'Akun anda telah dikonfirmasi dan diaktifkan, Anda dipersilahkan untuk log-in.',
// - account/confirmemail
'EmailConfirmTitle' => 'Konfirmasi E-mail',
'EmailConfirmFailed' => 'Terdapat masalah teknis ketika memperbaruhi alamat e-mail baru, harap hubungi admin.',
'EmailConfirmChanged' => 'Alamat e-mail Anda telah diganti!',
// - account/create
'AccountCreateTitle' => 'Buat Akun',
'AccountConfirmBan' => 'Menunggu aktivasi akun: %s',
'AccountCreateEmailSent' => 'Sebuah e-mail telah dikirimkan dan berisi keterangan tentang detail akun Anda, harap periksa e-mail Anda untuk aktifasi.',
'AccountCreateFailed' => 'Akun Anda telah dibuat, tetapi kami gagal untuk mengirimkan e-mail kepada Anda. Harap hubungi admin untuk mendapatkan bantuan.',
'AccountCreated' => 'Selamat, Anda telah terdaftar dan login secara otomatis.',
'AccountCreateHeading' => 'Pendaftaran Akun Baru',
'AccountCreateTerms' => 'Syarat dan Ketentuan',
'AccountCreateInfo' => 'Anda diharapkan untuk membaca %s sebelum membuat akun baru, untuk memastikan bahwa Anda mengerti semua ketentuan yang berlaku di private server kami.',
'AccountCreateInfo2' => 'Dengan menekan tombol "Buat Akun Baru", Anda setuju untuk taat dan mengikuti %s kami.',
'AccountCreateGenderInfo' => "Jenis kelamin yang Anda pilih akan menentukan karakter Anda di dalam game!",
'AccountServerLabel' => 'Server',
'AccountUsernameLabel' => 'Nama Pengguna',
'AccountPasswordLabel' => 'Kata Sandi',
'AccountPassConfirmLabel' => 'Konfirmasi Kata Sandi',
'AccountEmailLabel' => 'Alamat E-mail',
'AccountEmailLabel2' => 'Konfirmasi alamat e-mail',
'AccountGenderLabel' => 'Jenis Kelamin',
'AccountBirthdateLabel' => 'Tanggal Lahir',
'AccountSecurityLabel' => 'Kode Keamanan',
'AccountCreateButton' => 'Buat Akun Baru', // "Create My Account?" bukan "new" ya?
'AccountInvalidChars' => "Nama pengguna hanya diperbolehkan menggunakan karakter: '%s'",
'InvalidLoginServer' => 'Login-server salah, ulangi lagi dengan memilih login-sevrer yang benar.',
'InvalidLoginCredentials' => 'Data login tidak sesuai, verifikasi data yang dimasukkan dan ulangi.',
'UnexpectedLoginError' => 'Terjadi kesalahan yang tidak diinginkan, ulangi beberapa saat lagi atau laporkan kepada admin.',
'CriticalLoginError' => 'Sesuatu yang buruk telah terjadi. Laporkan ke administartor SEGERA!',
'UsernameAlreadyTaken' => 'Nama pengguna yang Anda pilih sudah digunakan oleh orang lain',
'UsernameTooShort' => sprintf('Nama pengguna Anda harus %d sampai %d karakter.', Flux::config('MinUsernameLength'), Flux::config('MaxUsernameLength')),
'UsernameTooLong' => sprintf('Nama pengguna Anda harus %d sampai %d karakter.', Flux::config('MinUsernameLength'), Flux::config('MaxUsernameLength')),
'PasswordContainsUser' => 'Tidak boleh terdapat nama pengguna di kata sandi.',
'PasswordHasUsername' => 'Tidak boleh terdapat nama pengguna di kata sandi.',
'PasswordTooShort' => 'Kata sandi harus %d sampai %d karakter.',
'PasswordTooLong' => 'Kata sandi harus %d sampai %d karakter.',
'PasswordsDoNotMatch' => 'Kata sandi tidak cocok, harap masukkan kedua kata sandi dengan benar',
'PasswordNeedUpper' => 'Kata sandi harus memiliki huruf BESAR setidaknya %d huruf.',
'PasswordNeedLower' => 'Kata sandi harus memiliki huruf kecil setidaknya %d huruf.',
'PasswordNeedNumber' => 'Kata sandi harus memiliki angka setidaknya %d digit.',
'PasswordNeedSymbol' => 'Kata sandi harus memiliki simbol setidaknya %d simbol.',
'EmailAddressInUse' => 'Alamat e-mail yang Anda masukkan sudah digunakan oleh pengguna lain. Masukkan alamat e-mail yang lain',
'InvalidEmailAddress' => 'Alamat e-mail yang Anda masukkan memiliki format yang tidak diizinkan',
'InvalidEmailconf' => "Alamat e-mail Anda tidak cocok.",
'InvalidGender' => 'Jenis kelamin yang diperbolehkan adalah "M" atau "F"',
'InvalidServer' => "Server yang Anda pilih tidak tersedia.",
'InvalidSecurityCode' => 'Silakan masukkan kode keamanan dengan benar.',
'InvalidPassword' => 'Kata sandi Anda mengandung karakter yang tidak diperbolehkan.',
'InvalidBirthdate' => 'Tanggal lahir Anda tidak benar.',
'CriticalRegisterError' => 'Error. Harap hubungi admin secepatnya.',
// - account/edit
'AccountEditTitle' => 'Ubah Akun',
'AccountEditTitle2' => 'Ubah Akun Saya',
'AccountEditTitle3' => 'Mengubah Akun (%s)',
'CannotModifyOwnGroupID' => 'Anda tidak bisa mengubah grup ID Anda sendiri.',
'CannotModifyAnyGroupID' => 'Anda tidak bisa mengubah grup ID.',
'CannotModifyGroupIDHigh' => 'Anda tidak bisa mengubah grup ID menjadi ID yang lebih tinggi dari akun Anda sendiri.',
'InvalidGroupID' => 'Grup ID salah.',
'CannotModifyBalance' => 'Anda tidak bisa mengubah jumlah kredit.',
'InvalidLastLoginDate' => 'Tanggal login terakhir salah.',
'InvalidVIPTime' => 'Waktu VIP salah.',
'AccountModified' => 'Akun telah berhasil diubah.',
'AccountEditHeading' => 'Ubah Akun',
'AccountEditButton' => 'Ubah Akun',
'AccountEditNotFound' => 'Akun tidak ditemukan.',
'VIPTimeDateLabel' => 'VIP Aktif Hingga',
// - account/index
'AccountIndexTitle' => 'Daftar Akun',
'AccountIndexHeading' => 'Akun',
'LoginBetweenLabel' => 'Login di antara',
'BirthdateBetweenLabel' => 'Tanggal lahir di antara',
'AccountIndexNotFound' => 'Akun tidak ditemukan.',
// - account/login
'LoginTitle' => 'Masuk Akun',
'LoginHeading' => 'Masuk Akun',
'LoginButton' => 'Masuk',
'LoginPageMakeAccount' => 'Belum punya akun? Daftarkan akun baru!',
'TemporarilyBanned' => 'Akun Anda telah diban untuk sementara.',
'PermanentlyBanned' => 'Akun Anda telah diban secara permanen.',
'IpBanned' => 'Alamat IP Anda telah diban.',
'PendingConfirmation' => 'Akun Anda masih menunggu aktivasi.',
// - account/logout
'LogoutTitle' => 'Logout',
'LogoutHeading' => 'Logout',
'LogoutInfo' => 'Anda telah logged out.',
'LogoutInfo2' => 'Harap tunggu beberapa saat, halaman sedang dialihkan…',
// - account/resend
'ResendTitle' => 'Kirim Ulang E-mail Konfirmasi',
'ResendEnterUsername' => 'Masukkan nama pengguna anda.',
'ResendEnterEmail' => 'Masukkan alamat e-mail.',
'ResendFailed' => 'Gagal mengirim ulang kode konfirmasi.',
'ResendEmailSent' => 'Kode konfirmasi anda telah dikirim ulang, silahkan cek e-mail dan ikuti langkah berikutnya untuk mengaktifkan akun.',
'ResendHeading' => 'Kirim Ulang E-mail Konfirmasi',
'ResendInfo' => 'Masukkan nama pengguna dan alamat e-mail yang digunakan ketika melakukan pendaftaran untuk pengiriman ulang e-mail konfirmasi',
'ResendServerLabel' => 'Server Terdaftar',
'ResendAccountLabel' => 'Nama Pengguna',
'ResendEmailLabel' => 'Alamat E-Mail',
'ResendServerInfo' => 'Server tempat akun didaftarkan.',
'ResendAccountInfo' => 'Nama pengguna yang didaftarkan.',
'ResendEmailInfo' => 'Alamat e-mail ini digunakan ketika Anda melakukan pendaftaran akun di atas.',
'ResendButton' => 'Kirim Ulang E-Mail Konfirmasi',
// - account/resetpass
'ResetPassTitle' => 'Reset Kata Sandi',
'ResetPassEnterAccount' => 'Masukkan nama pengguna.',
'ResetPassEnterEmail' => 'Masukkan alamat e-mail.',
'ResetPassDisallowed' => 'Pemulihan kata sandi tidak dapat digunakan untuk akun ini.',
'ResetPassFailed' => 'Gagal untuk mengirim e-mail pemulihan akun.',
'ResetPassEmailSent' => 'E-mail berisi detail untuk menyetel ulang kata sandi sudah dikiriman.',
'ResetPassInfo' => 'Jika Anda lupa kata sandi, Anda dapat menyetel ulang dengan cara memasukkan alamat e-mail yang digunakan ketika mendaftarkan akun.',
'ResetPassInfo2' => 'Alamat e-mail harus benar untuk melakukan pengiriman sebuah e-mail ke alamat yang dicantumkan yang berisi tautan untuk menyetel ulang kata sandi akun Anda.',
'ResetPassServerLabel' => 'Server Terdaftar',
'ResetPassAccountLabel' => 'Nama Pengguna',
'ResetPassEmailLabel' => 'Alamat E-Mail',
'ResetPassServerInfo' => 'Server tempat akun didaftarkan.',
'ResetPassAccountInfo' => 'Nama pengguna yang didaftarkan.',
'ResetPassEmailInfo' => 'Alamat e-mail ini digunakan ketika Anda melakukan pendaftaran akun di atas.',
'ResetPassButton' => 'Kirim E-mail Penyetelan Ulang Kata Sandi',
// - account/resetpw
'ResetPwTitle' => 'Reset Kata Sandi',
'ResetPwFailed' => 'Gagal untuk menyetel ulang kata sandi, ulangi beberapa saat lagi.',
'ResetPwDone' => 'Kata sandi telah disetel ulang. Sebuah e-mail berisi kata sandi baru telah dikirimkan, silahkan cek e-mail Anda.',
'ResetPwDone2' => 'Kata sandi sudah disetel ulang, tetapi e-mail yang berisi kata sandi baru gagal untuk dikirim. Silahkan ulangi menyetel ulang kata sandi.',
// - account/transfer
'TransferTitle' => 'Transfer Kredit',
'TransferGreaterThanOne' => 'Anda harus mengirimkan jumlah kredit yang lebih besar dari 1.',
'TransferEnterCharName' => 'Anda harus mengisi nama karakter penerima kredit.',
'TransferNoCharExists' => "Karakter '%s' tidak ditemukan. Harap periksa kembali nama karakter tersebut.",
'TransferNoBalance' => 'Anda tidak memiliki jumlah kredit yang cukup untuk melakukan transfer.',
'TransferUnexpectedError' => 'Error! Harap hubungi admin.',
'TransferSuccessful' => 'Kredit telah dikirim!',
'TransferHeading' => 'Transfer Kredit',
'TransferSubHeading' => 'Kredit akan ditransfer kepada karakter di server %s .',
'TransferInfo' => 'Kredit sekarang %s .',
'TransferInfo2' => 'Masukkan jumlah yang ingin Anda transfer dan nama karakter yang berada di akun yang ingin Anda tuju.',
'TransferAmountLabel' => 'Jumlah Kredit',
'TransferCharNameLabel' => 'Nama Karakter',
'TransferAmountInfo' => 'Jumlah kredit yang akan Anda kirim.',
'TransferCharNameInfo' => 'Nama karakter penerima kredit.',
'TransferConfirm' => 'Apakah Anda yakin?',
'TransferButton' => 'Transfer',
'TransferNoCredits' => 'Anda tidak memiliki kredit di akun Anda.',
// - account/view
'VIPStateLabel' => 'Status VIP',
// * account/view submenus
'ModifyAccountLink' => 'Ubah Akun',
'AccountViewTitle' => 'Lihat Akun',
'AccountViewTitle2' => 'Sedang Melihat Akun (%s)',
'AccountViewTitle3' => 'Akun Saya',
'AccountTempBanFailed' => 'Gagal melakukan ban sementara pada akun.',
'AccountPermBanFailed' => 'Gagal melakukan ban permanen pada akun.',
'AccountTempBanUnauth' => 'Anda tidak dapat melakukan ban sementara pada akun ini.',
'AccountPermBanUnauth' => 'Anda tidak dapat melakukan ban permanen pada akun ini.',
'AccountLiftTempBan' => 'Ban sementara pada akun ini telah dibatalkan.',
'AccountLiftPermBan' => 'Ban permanen pada akun ini telah dibatalkan.',
'AccountLiftBanUnauth' => "Anda tidak dapat menghapus status ban pada akun ini.",
'AccountViewHeading' => 'Melihat Akun',
'AccountViewDonateLink' => '(Donasi!)',
'AccountViewTempBanLabel' => 'Ban Sementara',
'AccountViewPermBanLabel' => 'Ban Permanen',
'AccountViewUnbanLabel' => 'Hapus Ban',
'AccountBanReasonLabel' => 'Alasan:',
'AccountBanUntilLabel' => 'Ban Sampai:',
'AccountTempBanButton' => 'Ban Sementara',
'AccountPermBanButton' => 'Ban Permanen',
'AccountTempUnbanButton' => 'Hapus Ban Sementara',
'AccountPermUnbanButton' => 'Hapus Ban Permanen',
'AccountBanConfirm' => 'Apakah Anda yakin?',
'AccountBanLogSubHeading' => 'Catatan Ban Untuk %s (Baru ke Lama)',
'BanLogBanTypeLabel' => 'Jenis Ban',
'BanLogBanDateLabel' => 'Tanggal Ban',
'BanLogBanReasonLabel' => 'Alasan Ban',
'BanLogBannedByLabel' => 'Pemberi Ban',
'BanLogBannedByCP' => 'Control Panel',
'BanTypeUnbanned' => 'Tidak di Ban',
'BanTypePermBanned' => 'Ban Permanen',
'BanTypeTempBanned' => 'Ban Sementara',
'AccountViewCharSubHead' => 'Karakter Pada %s',
'AccountViewSlotLabel' => 'Slot',
'AccountViewCharLabel' => 'Nama Karakter',
'AccountViewClassLabel' => 'Job Class',
'AccountViewLvlLabel' => 'Base Level',
'AccountViewJlvlLabel' => 'Job Level',
'AccountViewZenyLabel' => 'Zeny',
'AccountViewGuildLabel' => 'Guild',
'AccountViewStatusLabel' => 'Status',
'AccountViewPrefsLabel' => 'Pengaturan',
'CharModifyPrefsLink' => 'Ubah Pengaturan',
'AccountViewNoChars' => 'Akun ini tidak memiliki karakter di %s.',
'AccountViewStorage' => 'Penyimpanan Barang Milik %s',
'AccountViewStorageCount' => '%s memiliki %s barang di penyimpanan.',
'AccountViewNoStorage' => 'Tidak ada apapun di dalam penyimpanan barang.',
'AccountViewNotFound' => "Akun yang Anda cari tidak ditemukan.",
// - account/xferlog
'XferLogTitle' => 'Catatan Transfer Kredit',
'XferLogHeading' => 'Catatan Transfer Kredit',
'XferLogReceivedSubHead' => 'Transfer: Diterima',
'XferLogSentSubHead' => 'Transfer: Dikirim',
'XferLogCreditsLabel' => 'Kredit',
'XferLogFromLabel' => 'Dari E-mail',
'XferLogDateLabel' => 'Tanggal Transfer',
'XferLogCharNameLabel' => 'Untuk Karakter',
'XferLogNotReceived' => 'Anda tidak menerima pengiriman kredit.',
'XferLogNotSent' => 'Anda tidak mengirim pengiriman kredit.',
// Module: character
// - character/changeslot
// - character/index
// - character/mapstats
// - character/online
// - character/prefs
// - character/resetlook
'CantResetLookWhenOnline' => 'Tidak dapat menyetel ulang penampilan %s ketika sedang online.',
'ResetLookSuccessful' => 'Penampilan %s telah disetel ulang!',
'ResetLookFailed' => 'Gagal untuk menyetel ulang penampilan %s.',
// - character/resetpos
'CantResetPosWhenOnline' => 'Tidak dapat menyetel ulang posisi %s ketika sedang online.',
'CantResetFromCurrentMap' => 'Anda tidak dapat menyetel ulang posisi %s di map saat ini.',
'ResetPositionSuccessful' => 'Posisi %s telah disetel ulang!',
'ResetPositionFailed' => 'Gagal untuk menyetel ulang posisi %s.',
// - character/view
// - character/divorce
'DivorceTitle' => 'Bercerai',
'DivorceHeading' => 'Bercerai',
'DivorceNotMarried' => '%s belum menikah dengan siapapun.',
'DivorceInvalidPartner' => 'ID pasangan tidak ditemukan.',
'DivorceInvalidChild' => 'ID adopsi tidak ditemukan.',
'DivorceMustBeOffline' => '%s dan pasangannya harus dalam keadaan offline.',
'DivorceMustBeOffline2' => '%s, pasangan, dan anaknya harus dalam keadaan offline.',
'DivorceText1' => "Apakah Anda ingin menceraikan dengan %s dan pasangannya?",
'DivorceText2' => 'Apabila %s memiliki adopsi, status adopsi akan hilang.',
'DivorceText3' => "Item 'Wedding ring' akan dihapus.",
'DivorceButton' => 'Ya.',
'DivorceSuccessful' => '%s telah bercerai!',
// Module: cplog
// - cplog/index.php
// - cplog/login.php
// - cplog/paypal.php
// - cplog/resetpass.php
// - cplog/txnview.php
// Module: donate
// - donate/complete
// - donate/history
// - donate/index
// - donate/trusted
// Module: errors
// - errors/missing_action
'MissingActionTitle' => 'Aksi Tidak Ditemukan',
'MissingActionHeading' => 'Aksi Tidak Ditemukan!',
'MissingActionModLabel' => 'Modul:',
'MissingActionActLabel' => 'Aksi:',
'MissingActionReqLabel' => 'URI Diminta:',
'MissingActionLocLabel' => 'Lokasi file:',
// - errors/missing_view
'MissingViewTitle' => 'View Tidak Ditemukan',
'MissingViewHeading' => 'View Tidak Ditemukan!',
'MissingViewModLabel' => 'Modul:',
'MissingViewActLabel' => 'Aksi:',
'MissingViewReqLabel' => 'URI Diminta:',
'MissingViewLocLabel' => 'Lokasi file:',
// Module: guild
// - guild/export
// - guild/index
// - guild/view
// Module: history
// - history/cplogin
'HistoryCpLoginTitle' => 'Login Control Panel',
'HistoryCpLoginHeading' => 'Login Control Panel',
'HistoryLoginDateLabel' => 'Waktu/Tanggal Login',
'HistoryIpAddrLabel' => 'Alamat IP',
'HistoryErrorCodeLabel' => 'Error Code',
'HistoryNoCpLogins' => 'Tidak ada percobaan login ke Control Panel.',
// -history/emailchange
'HistoryEmailTitle' => 'Perubahan E-Mail',
'HistoryEmailHeading' => 'Perubahan E-Mail',
'HistoryEmailRequestDate' => 'Waktu/Tanggal Permohonan',
'HistoryEmailRequestIp' => 'IP Pemohon',
'HistoryEmailOldAddress' => 'E-Mail Lama',
'HistoryEmailNewAddress' => 'E-Mail Baru',
'HistoryEmailChangeDate' => 'Tanggal Perubahan',
'HistoryEmailChangeIp' => 'IP Perubahan',
'HistoryNoEmailChanges' => 'Tidak ada percobaan perubahan e-mail.',
// - history/gamelogin
'HistoryGameLoginTitle' => 'Masuk ke Game',
'HistoryGameLoginHeading' => 'Masuk ke Game',
'HistoryRepsCodeLabel' => 'Kode Respon',
'HistoryLogMessageLabel' => 'Catatan',
'HistoryNoGameLogins' => 'Tidak ada percobaan Masuk ke Game.',
// - history/index
'HistoryIndexTitle' => 'Aktivitas Akun',
'HistoryIndexHeading' => 'Aktivitas Akun',
'HistoryIndexInfo' => 'Anda dapat melihat aktivitas yang telah lalu untuk akun Anda.',
'HistoryIndexInfo2' => 'Harap pilih aksi pada menu.',
// - history/passchange
'HistoryPassChangeTitle' => 'Perubahan Kata Sandi',
'HistoryPassChangeHeading' => 'Perubahan Kata Sandi',
'HistoryPassChangeChangeDate' => 'Tanggal Perubahan',
'HistoryPassChangeChangeIp' => 'IP Perubahan',
'HistoryNoPassChanges' => 'Tidak ada perubahan kata sandi.',
// -history/passreset
'HistoryPassResetTitle' => 'Penyetelan Ulang Kata Sandi',
'HistoryPassResetHeading' => 'Penyetelan Ulang Kata Sandi',
'HistoryPassResetRequestDate' => 'Tanggal Meminta',
'HistoryPassResetRequestIp' => 'IP Peminta',
'HistoryPassResetResetDate' => 'Tanggal Penyetelan',
'HistoryPassResetResetIp' => 'IP Penyetelan',
'HistoryNoPassResets' => 'Tidak ada percobaan merubah kata sandi.',
// Module: ipban
// - ipban/add
'IpbanAddTitle' => 'Tambah Ban IP',
'IpbanEnterIpPattern' => 'Harap masukkan alamat IP atau pola.',
'IpbanInvalidPattern' => 'Alamat IP atau pola tidak sesuai.',
'IpbanWhitelistedPattern' => 'Pola ini ada di whitelist dan tidak dapat diblokir.',
'IpbanEnterReason' => 'Harap masukkan alasan',
'IpbanSelectUnbanDate' => 'Tanggal unban dibutuhkan.',
'IpbanFutureDate' => 'Tanggal unban harus tanggal yang akan datang.',
'IpbanAlreadyBanned' => 'IP yang sesuai dengan (%s) telah diban.',
'IpbanPatternBanned' => "Pola/alamat IP '%s' telah diban.",
'IpbanAddFailed' => 'Gagal menambahkan IP yang diban.',
'IpbanAddHeading' => 'Tambahkan Ban IP',
'IpbanIpAddressLabel' => 'Alamat IP',
'IpbanReasonLabel' => 'Alasan Ban',
'IpbanUnbanDateLabel' => 'Tanggal Unban',
'IpbanIpAddressInfo' => 'Anda boleh memasukkan pola serperti 218.139.*.*',
'IpbanAddButton' => 'Tambahkan Ban IP',
// - ipban/edit
'IpbanEditTitle' => 'Ubah Ban IP',
'IpbanEnterEditReason' => 'Harap masukkan alasan untuk perubahan ban IP',
'IpbanEditFailed' => 'Gagal untuk mengubah ban IP.',
'IpbanEditHeading' => 'Ubah Ban IP',
'IpbanEditReasonLabel' => 'Ubah Alasan',
'IpbanEditButton' => 'Modifikasi Ban IP',
// - ipban/index
'IpbanListTitle' => 'Daftar Ban IP',
'IpbanListHeading' => 'Daftar Ban IP Ban',
'IpbanBannedIpLabel' => 'Banned IP',
'IpbanBanDateLabel' => 'Tanggal Ban',
'IpbanBanReasonLabel' => 'Alasan Ban',
'IpbanBanExpireLabel' => 'Tanggal Berakhir Ban',
'IpbanModifyLink' => 'Ubah',
'IpbanRemoveLink' => 'Hilangkan',
'IpbanUnbanButton' => 'Unban yang dipilih',
'IpbanListNoBans' => 'Tidak ada ban IP saat ini.',
// - ipban/remove
'IpbanRemoveTitle' => 'Hilangkan Ban IP',
'IpbanEnterRemoveReason' => 'Harap masukkan alasan untuk penghilangan ban IP.',
'IpbanNotBanned' => 'IP (%s) tidak ditemukan.',
'IpbanPatternUnbanned' => "Pola/alamat IP '%s' telah di-unban.",
'IpbanRemoveFailed' => 'Gagal untuk menghilangkan ban IP.',
'IpbanRemoveHeading' => 'Hilangkan Ban IP',
'IpbanRemoveReasonLabel' => 'Alasan Unban',
'IpbanRemoveButton' => 'Hilangkan Ban IP',
// - ipban/unban
'IpbanNothingToUnban' => 'Tidak ada yang di-unban.',
'IpbanEnterUnbanReason' => 'Harap masukkan alasan untuk pengangkatan ban IP.',
'IpbanUnbanned' => 'Ban IP telah dihilangkan!',
'IpbanUnbanFailed' => 'Gagal untuk menghilangkan %d ban IP!',
// Module: item
// - item/add
// - item/copy
// - item/edit
// - item/index
// - item/view
// Module: itemshop
// - itemshop/add
// - itemshop/delete
// - itemshop/edit
// - itemshop/imagedel
// Module: logdata
// - logdata/chat
// - logdata/cashpoints
'CashLogTitle' => 'Daftar Log CashPoints',
'CashLogHeading' => 'Log CashPoint',
'CashLogNotFound' => 'Tidak ada log CashPoint.',
'CashLogDateLabel' => 'Tanggal/Jam',
'CashLogCharacterLabel' => 'Karakter',
'CashLogTypeLabel' => 'Tipe',
'CashLogCashTypeLabel' => 'Tipe Cash',
'CashLogAmountLabel' => 'Jumlah',
'CashLogMapLabel' => 'Map',
// - logdata/command
'CommandLogTitle' => 'Daftar Perintah',
'CommandLogHeading' => 'Perintah',
'CommandLogNotFound' => 'Perintah tidak ditemukan.',
'CommandLogDateLabel' => 'Tanggal/Jam',
'CommandLogAccountIdLabel'=> 'ID Akun',
'CommandLogCharIdLabel' => 'ID Karakter',
'CommandLogCharNameLabel' => 'Nama Karakter',
'CommandLogCommandLabel' => 'Perintah',
'CommandLogMapLabel' => 'Map',
// - logdata/index
// - logdata/login
// - logdata/pick
'PickLogTitle' => 'Daftar Barang yang Diambil',
'PickLogHeading' => 'Barang Diambil',
'PickLogNotFound' => 'Tidak ada barang yang diambil.',
'PickLogDateLabel' => 'Tanggal/Jam',
'PickLogCharacterLabel' => 'Karakter',
'PickLogTypeLabel' => 'Tipe',
'PickLogItemLabel' => 'Nama Barang',
'PickLogAmountLabel' => 'Jumlah',
'PickLogRefineLabel' => 'Tempa',
'PickLogCard0Label' => 'Kartu 1',
'PickLogCard1Label' => 'Kartu 2',
'PickLogCard2Label' => 'Kartu 3',
'PickLogCard3Label' => 'Kartu 4',
'PickLogMapLabel' => 'Map',
// - logdata/branch
'BranchLogTitle' => 'Daftar Branch',
'BranchLogHeading' => 'Branch Log',
'BranchLogNotFound' => 'Tidak ada log branch yang ditemukan.',
'BranchLogIDLabel' => 'ID Branch Log',
'BranchLogDateLabel' => 'Tanggal/Jam',
'BranchLogAccountIDLabel' => 'ID Akun',
'BranchLogCharIDLabel' => 'ID Karakter',
'BranchLogCharNameLabel' => 'Nama Karakter',
'BranchLogMapLabel' => 'Map',
// - logdata/char
'CharLogTitle' => 'Dafatr Log Karakter',
'CharLogHeading' => 'Log Karakter',
'CharLogNotFound' => 'Tidak ada log karakter yang ditemukan.',
'CharLogDateLabel' => 'Tanggal/Jam',
'CharLogMsgLabel' => 'Aksi',
'CharLogAccountIDLabel' => 'ID Akun',
'CharLogCharNumLabel' => 'Slot karakter',
'CharLogCharNameLabel' => 'Nama Karakter',
// - logdata/inter
'InterLogTitle' => 'Daftar Log Interaksi',
'InterLogHeading' => 'Log Interaksi',
'InterLogNotFound' => 'Tidak ada log interaksi yang diteukan.',
'InterLogDateLabel' => 'Tanggal/Jam',
'InterLogLabel' => 'Log Interaksi',
// - logdata/mvp
'MVPLogTitle' => 'Daftar Log MVP',
'MVPLogHeading' => 'Log MVP',
'MVPLogNotFound' => 'Tidak ada log MVP yang ditemukan.',
'MVPLogIDLabel' => 'ID Log MVP',
'MVPLogDateLabel' => 'Tanggal/Jam',
'MVPLogCharacterLabel' => 'ID Karakter',
'MVPLogMonsterLabel' => 'Monster MVP',
'MVPLogPrizeLabel' => 'Hadiah MVP',
'MVPLogExpLabel' => 'Exp. MVP',
'MVPLogMapLabel' => 'Map',
// - logdata/npc
'NPCLogTitle' => 'Daftar Log NPC',
'NPCLogHeading' => 'Log NPC',
'NPCLogNotFound' => 'Tidak ada log NPC yang ditemukan.',
'NPCLogIDLabel' => 'ID NPC',
'NPCLogDateLabel' => 'Tanggal/Jam',
'NPCLogAccountIDLabel' => 'ID Akun',
'NPCLogCharIDLabel' => 'ID Karakter',
'NPCLogCharNameLabel' => 'Nama Karakter',
'NPCLogMapLabel' => 'Map',
'NPCLogMsgLabel' => 'Pesan',
// - logdata/zeny
'ZenyLogTitle' => 'Dafatr Log Zeny',
'ZenyLogHeading' => 'Log Zeny',
'ZenyLogNotFound' => 'Tidak ada log Zeny yang ditenukan.',
'ZenyLogDateLabel' => 'Tanggal/Jam',
'ZenyLogCharacterLabel' => 'Karakter',
'ZenyLogSourceLabel' => 'Sumber',
'ZenyLogTypeLabel' => 'Tipe',
'ZenyLogAmountLabel' => 'Jumlah',
'ZenyLogMapLabel' => 'Map',
// Module: mail
// - mail/index
'MailerTitle' => 'Formulir Pesan (Form Mailer)',
'MailerHeading' => 'Formulir Pesan',
'MailerEnterToAddress' => 'Harap masukkan alamat tujuan.',
'MailerEnterSubject' => 'Harap masukkan judul.',
'MailerEnterBodyText' => 'Harap masukkan beberapa isi pesan.',
'MailerEmailHasBeenSent' => 'E-mail anda telah sukses dikirim ke %s.',
'MailerFailedToSend' => 'Sistem pengiriman pesan gagal untuk mengirim e-mail. Mungkin terjadi karena kesalahan konfigurasi.',
'MailerInfo' => 'Anda diperbolehkan menggunakan formulir pesan di bawah ini untuk mengirimkan e-mail menggunakan control panel.',
'MailerFromLabel' => 'Dari',
'MailerToLabel' => 'Tujuan',
'MailerSubjectLabel' => 'Judul',
'MailerBodyLabel' => 'isi',
'MailerSelectTemplateLabel' => 'Pilih Template',
// Module: main
// - main/index
'MainPageHeading' => 'Flux Control Panel',
'MainPageInfo' => 'Jika Anda melihat halaman ini, berarti Flux telah berhasil diinstal.',
'MainPageInfo2' => 'Yakin untuk mengubah halaman ini?',
'MainPageStep1' => 'Membuka "%s" di teks editor.',
'MainPageStep2' => 'Ubah file dari editor dan simpan perubahan.',
'MainPageThanks' => 'Terima kasih telah menggunakan Flux!',
'MainPageWelcome' => 'Selamat datang di %s!',
// - main/pagenotfound
'PageNotFoundTitle' => '404 Page Not Found',
'PageNotFoundHeading' => 'Halaman Tidak Ditemukan',
'PageNotFoundInfo' => 'Halaman yang diminta tidak ditemukan. Harap periksa kembali alamat yang dimasukkan.',
// - main/preprocess
'DisallowedDuringWoE' => 'Halaman yang diminta tidak dapat diakses ketika WoE.',
// Module: monster
// - monster/index
// - monster/view
// Module: purchase
// - purchase/add
// - purchase/cart
// - purchase/checkout
// - purchase/clear
// - purchase/index
// - purchase/pending
// - purchase/remove
// Module: ranking
// - ranking/character
// - ranking/guild
// - ranking/zeny
// Module: server
// - server/info
'ServerInfoTitle' => 'Informasi Server',
'ServerInfoHeading' => 'Informasi Server',
'ServerInfoText' => 'Di sini Anda akan menemukan berbagai informasi tentang server.',
'ServerInfoSubHeading' => 'Informasi untuk %s',
'ServerInfoSubHeading2' => 'Informasi Class untuk %s',
'ServerInfoAccountLabel' => 'Akun',
'ServerInfoCharLabel' => 'Karakter',
'ServerInfoGuildLabel' => 'Guild',
'ServerInfoPartyLabel' => 'Parties',
'ServerInfoZenyLabel' => 'Zeny',
// - server/status
'ServerStatusTitle' => 'Status Server Saat Ini',
'ServerStatusHeading' => 'Status Server',
'ServerStatusInfo' => "Berikut ini adalah status dari tiap server.",
'ServerStatusServerLabel' => 'Server',
'ServerStatusLoginLabel' => 'Login Server',
'ServerStatusCharLabel' => 'Character Server',
'ServerStatusMapLabel' => 'Map Server',
'ServerStatusOnlineLabel' => 'Pemain Online',
'ServerStatusPeakLabel' => 'Pemain Terbanyak',
// Module: service
// - service/tos
'TermsTitle' => 'Syarat dan Ketentuan',
'TermsHeading' => 'Syarat dan Ketentuan',
'TermsInfo' => 'Harap dibaca sebelum Anda membuat akun!',
'TermsInfo2' => "FOR CONTROL PANEL ADMINISTRATOR: Anda harus menambahkan server ToS pada tampilan ini secara langsung. Lokasi dari file ini adalah: %s",
// Module: unauthorized
// - unauthorized/index
'UnauthorizedTitle' => 'Tidak Diperbolehkan',
'UnauthorizedHeading' => 'Tidak Diperbolehkan',
'UnauthorizedInfo' => 'Anda tidak diperbolehkan untuk melihat halaman ini. Kembali ke sebelumnya…',
// Module: woe
// - woe/index
'WoeTitle' => 'Jadwal WoE',
'WoeHeading' => 'Jadwal War of Emperium',
'WoeInfo' => 'Di bawah ini adalah jadwal WoE untuk %d. Jadwal berikut mungkin bisa berubah sewaktu-waktu.',
'WoeServerTimeInfo' => 'Waktu server saat ini adalah:',
'WoeServerLabel' => 'Server',
'WoeTimesLabel' => 'Jadwal War of Emperium',
'WoeNotScheduledInfo' => 'Tidak ada jadwal WoE saat ini.',
// Module: contactform
'CFTitleSubmit' => 'Hubungi Kami',
// Module: News and Pages
'CMSNewsHeader' => 'Pengumuman',
'CMSPageHeader' => 'Content Management System',
'CMSPageText' => 'Modul ini mengizinkan admin dan staff untuk membuat halaman pada website tanpa perlu pengetahuan tentang Flux/pemrograman. Sistem berita internal juga dapat ekspor untuk RSS Feed dengan mengubah pengaturan aplikasi.',
'CMSNewsTitleError' => 'Judul Berita harus diisi!',
'CMSNewsBodyError' => 'Isi Berita harus diisi!',
'CMSPageTitleError' => 'Judul Halaman harus diisi!',
'CMSPageBodyError' => 'Isi Halaman harus diisi!',
'CMSPagePathError' => 'Lokasi Halaman harus diisi!',
'CMSNewsAdded' => 'Berita telah ditambahkan',
'CMSPagesAdded' => 'Halaman telah ditambahkan',
'CMSNewsUpdated' => 'Berita telah diperbaruhi',
'CMSPageUpdated' => 'Halaman telah diperbaruhi',
'CMSNewsAddTitle' => 'Menambahkan isi beritaAdd a news item',
'CMSPageAddTitle' => 'Membuat halaman baru',
'CMSNewsEditTitle' => 'Mengubah berita',
'CMSPageEditTitle' => 'Mengubah halaman',
'CMSNewsNotFound' => 'Berita tidak ditemukan!',
'CMSPageNotFound' => 'Halaman tidak ditemukan!',
'CMSNewsDeleted' => 'Berita telah dihapus',
'CMSPageDeleted' => 'Halamna telah dihapus',
'CMSNewsEmpty' => 'Tidak ada bertia yang ditemukan. Cek kembali Tipe Berita di pengaturan? (pengaturan CMSNewsType)',
'CMSNewsRSSNotFound' => "RSS feed tidak dapat ditemukan. Cek kembali pengaturan CMSNewsRSS, atau ganti tipe CMSNewsType menjadi 1 untuk menggunakan sistem berita internal!",
'CMSPageEmpty' => 'Tidak ada halaman',
'CMSNewsLink' => 'baca selengkapnya...',
'CMSEdit' => 'Perbaruhi',
'CMSDelete' => 'Hapus',
'CMSNewsTitleLabel' => 'Judul Berita',
'CMSNewsBodyLabel' => 'Isi Berita',
'CMSNewsLinkLabel' => 'Tautan Berita',
'CMSNewsAuthorLabel' => 'Penulis Berita',
'CMSPageTitleLabel' => 'Judul Halaman',
'CMSPageBodyLabel' => 'Isi Halaman',
'CMSPagePathLabel' => 'Lokasi Halaman',
'CMSCreatedLabel' => 'Tanggal Pembuatan',
'CMSModifiedLabel' => 'Tanggal Diperbaruhi',
'CMSActionLabel' => 'Tindakan',
'CMSConfirmDeleteLabel' => 'Apakah yakin untuk menghapus?',
'CMSPageCreate' => 'Apakah yakin membuat halaman?',
'CMSOptionalLabel' => '(Opsional)',
'CMSRequiredLabel' => '(Harus Diisi)',
'CMSCreateLabel' => 'Buat Berita Baru',
// Module: vending
'TLHeaderTasks' => 'Tugas',
'TLHeaderOwner' => 'Pemilik',
'TLHeaderPriority' => 'Prioritas',
'TLHeaderStatus' => 'Status',
'TLHeaderCreated' => 'Dibuat',
'TLHeaderModified' => 'Diubah',
'TLHeaderResources' => 'Bahan Tambahan',
'TLHeaderBody' => 'Isi',
// Module: servicedesk
'SDHeader' => 'Layanan Bantuan',
'SDCreateNew' => 'Kirim Laporan',
'SDWelcomeText' => 'Selamat datang di Layanan Bantuan',
'SDNoTickets' => 'Anda tidak memiliki laporan apapun.',
'SDNoBlankResponse' => 'Anda harus mengisi data ke dalam formulir!',
'SDNoCatsAvailable' => 'Tidak ada kategori yang tersedia',
'SDNoOpenTickets' => 'Tidak ada laporan di database.',
'SDNoInactiveTickets' => "Tidak ada laporan yang 'tidak aktif' di database.",
'SDNoClosedTickets' => "Tidak ada laporan yang 'ditutup' di database.",
'SDNoCats' => 'Tidak ada kategori di database.',
'SDHuh' => 'Error',
'SDPointerChatLog' => 'Kami sarankan untuk menggunakan pastebin.com lalu kirimkan tautannya pada kami.',
'SDPointerScreenShot' => 'Kirimkan juga tautan dari gambar atau screenshot yang Anda punya',
'SDPointerVideoLink' => 'Kami sarankan untuk mengunggah video Anda ke YouTube, lalu kirimkan tautannya pada kami.',
'SDHeaderID' => 'Laporan #',
'SDHeaderSubject' => 'Subjek',
'SDHeaderCategory' => 'Kategori',
'SDHeaderStatus' => 'Status',
'SDHeaderLastAuthor' => 'Penulis Terakhir',
'SDHeaderTimestamp' => 'Dibuat',
'SDHeaderAccount' => 'Akun',
'SDHeaderTeam' => 'Tim',
'SDH3ActiveTickets' => 'Laporan Aktif',
'SDH3InActiveTickets' => 'Laporan Tidak Aktif',
'SDH3ClosedTickets' => 'Laporan Ditutup',
'SDH3CurrentCat' => 'Kategori',
'SDH3CreateCat' => 'Buat Kategori Baru',
'SDH3StaffList' => 'Pengaturan Staf',
'SDH3StaffCreate' => 'Tambah Staf',
'SDReOpenPlayer' => 'Laporan diaktifkan kembali oleh pemain',
'SDReOpenStaff' => '',
'SDRespTable1' => 'Balas dan Kembali ke Laporan',
'SDRespTable2' => 'Balas dan Kembali ke Daftar Laporan',
'SDRespTable3' => 'Balas dan Tutup Laporan',
'SDRespTable4' => 'Balas dan Tingkatkan Status',
'SDRespTable5' => 'Tutup Laporan',
'SDRespTable6' => 'Balas dan Aktifkan Laporan',
'SDRespTable7' => 'Resolve Ticket dan Credit Account',
'SDGroup1' => 'Staf Layanan Bantuan',
'SDGroup2' => 'Kepala Staf Layanan Bantuan',
'SDGroup3' => 'Administrasi',
'SDLinkOpenNew' => 'Buat laporan baru',
// Module: webcommands
'WCTitleLabel' => 'Web Commands',
);
?>
================================================
FILE: lang/pt_br.php
================================================
'Portuguese',
'YesLabel' => 'Sim',
'NoLabel' => 'Não',
'NoteLabel' => 'Nota',
'GenderTypeMale' => 'Masculino',
'GenderTypeFemale' => 'Feminino',
'GenderTypeServer' => 'Servidor',
'RefreshSecurityCode' => 'Atualizar Código de Segurança',
'NoneLabel' => 'Nada',
'NeverLabel' => 'Nunca',
'NotApplicableLabel' => 'Não Aplicável',
'UnknownLabel' => 'Desconhecido',
'IsEqualToLabel' => 'é igual a',
'IsGreaterThanLabel' => 'é maior que',
'IsLessThanLabel' => 'é menor que',
'AllLabel' => 'Tudo',
'SearchLabel' => 'Procurar…',
'GoBackLabel' => 'Voltar à página anterior…',
'SearchButton' => 'Procurar',
'ResetButton' => 'Resetar',
'FilterButton' => 'Filtrar',
'NotAcceptingDonations' => "Desculpe, mas nós não estamos aceitando doações no momento. Desculpas pela inconveniência.",
//'NotAcceptingDonations' => "We're sorry, but our donation system is currently undergoing maintenance, please try again later.",
'FoundSearchResults' => 'Encontrado um total de %d registro(s) em %d páginas(s). Mostrando %d-%d.',
'LoginToDonate' => 'Por favor, faça login para poder doar.',
'UnknownCharacter' => 'Nenhum personagem encontrado.',
'AccountIdLabel' => 'ID da Conta',
'AccountGroupIDLabel' => 'Level do Grupo',
'AccountStateLabel' => 'Status da Conta',
'CreditBalanceLabel' => 'Balanço de Crédito',
'UsernameLabel' => 'Usuário',
'PasswordLabel' => 'Senha',
'EmailAddressLabel' => 'E-mail',
'GenderLabel' => 'Gênero',
'LoginCountLabel' => 'Contagem de Login',
'LastLoginDateLabel' => 'Data do último Login',
'LastUsedIpLabel' => 'Último IP registrado',
'AccountStateNormal' => 'Normal',
'AccountStatePending' => 'Pendente',
'AccountStatePermBanned' => 'Banido Permanentemente',
'AccountStateTempBanLbl' => 'Banido Temporariamente',
'AccountStateTempBanned' => 'Banido Temp. (Desbanir: %s)',
'OnlineLabel' => 'Online',
'OfflineLabel' => 'Offline',
'ItemIdLabel' => 'ID do Item',
'ItemNameLabel' => 'Nome do Item',
'ItemAmountLabel' => 'Quantidade',
'ItemIdentifyLabel' => 'Identificado',
'ItemRefineLabel' => 'Refinado',
'ItemBrokenLabel' => 'Quebrado',
'ItemCard0Label' => 'Carta 1',
'ItemCard1Label' => 'Carta 2',
'ItemCard2Label' => 'Carta 3',
'ItemCard3Label' => 'Carta 4',
// Security
'SecuritySessionInvalid' => 'Desculpe, a sessão expirou, tente novamente.',
'SecurityNeedSession' => 'Desculpe, nenhuma sessão foi encontrada (tentativa de hack?)',
'SecurityNeedToken' => 'Desculpe, nenhum sinal encontrado para identificar esta forma (tentativa de hack?)',
// Module: account
// - account/changemail
'EmailChangeTitle' => 'Alterar E-mail',
'EnterEmailAddress' => 'Insira um E-mail.',
'EmailCannotBeSame' => 'Seu novo e-mail não pode ser igual ao atual.',
'EmailInvalid' => 'Endereço de e-mail inválido.',
'EmailAlreadyRegistered' => "Esse e-mail já está registrado para outra conta, insira outro.",
'EmailChangeSent' => 'Uma mensagem foi enviada para o seu novo email com um link para confirmar a alteração.',
'EmailAddressChanged' => 'Seu e-mail foi alterado com sucesso!',
'EmailChangeFailed' => 'Falha ao alterar e-mail. Tente novamente mais tarde.',
'EmailChangeHeading' => 'Alterar E-mail',
'EmailChangeInfo' => 'Se você quer alterar o e-mail cadastrado na sua conta, preencha o formulário abaixo.',
'EmailChangeInfo2' => 'Depois de enviar o formulário, você receberá uma mensagem no seu novo email contendo um link para você confirmar a sua alteração.',
'EmailChangeLabel' => 'Novo E-mail',
'EmailChangeInputNote' => 'Deve ser um e-mail válido!',
'EmailChangeButton' => 'Alterar E-mail',
// - account/changepass
'PasswordChangeTitle' => 'Alterar Senha',
'NeedCurrentPassword' => 'Insira a sua senha atual.',
'NeedNewPassword' => 'Insira a sua nova senha.',
'OldPasswordInvalid' => "A senha que você digitou não bate com a sua senha real.",
'ConfirmNewPassword' => 'Confirme sua nova senha.',
'NewPasswordHasUsername' => 'Sua nova senha não deve conter seu nome de usuário..',
'NewPasswordInvalid' => 'Senha alterada com sucesso, favor faça login novamente.',
'NewPasswordSameAsOld' => 'Nova senha não pode ser a mesma que sua senha atual.',
'NewPasswordNeedUpper' => 'Sua nova senha deve conter pelo menos %d letra(s) maiúsculas.',
'NewPasswordNeedLower' => 'Sua nova senha deve conter pelo menos %d letra(s) minúsculas.',
'NewPasswordNeedNumber' => 'Sua nova senha deve conter pelo menos %d número(s).',
'NewPasswordNeedSymbol' => 'Sua nova senha deve conter pelo menos %d símbolo (s).',
'PasswordHasBeenChanged' => 'Sua senha foi alterada, por favor, faça log-in novamente.',
'FailedToChangePassword' => 'Falha ao alterar sua senha. Por favor, contate um administrador.',
'PasswordChangeHeading' => 'Alterar sua Senha',
'PasswordChangeInfo' => 'Por favor, digite sua senha atual e, em seguida, digite a nova senha que deseja usar e digite novamente para confirmar.',
'CurrentPasswordLabel' => 'Senha Atual',
'NewPasswordLabel' => 'Nova Senha',
'NewPasswordConfirmLabel' => 'Re-digite a nova senha',
'PasswordChangeNote' => 'Por favor, certifique-se de digitar as informações corretas.',
'PasswordChangeNote2' => 'Após alterar sua senha, você será registrado.',
'PasswordChangeButton' => 'Alterar Senha',
// - account/changesex
'GenderChangeTitle' => 'Alterar Gênero',
'GenderChangeBadChars' => 'Você não pode trocar de gênero se algum dos seus personagens é um %s',
'GenderChanged' => 'Seu gênero foi alterado com sucesso e $d créditos foram debitados sua conta.',
'GenderChangedForFree' => 'Gênero alterado com sucesso.',
'GenderChangeHeading' => 'Altere Seu Gênero',
'GenderChangeCost' => 'Mudança de Gênero vai custar %s créditos para você.',
'GenderChangeBalance' => 'Você possui atualmente %s créditos.',
'GenderChangeNoFunds' => 'Você não possui créditos suficiente para completar a sua troca de gênero.',
'GenderChangeNoCost' => 'Para você, as mudanças de sexo são livres.',
'GenderChangeCharInfo' => 'Você não pode alterar seu gênero se você tiver um personagem que for um: %s',
'GenderChangeSubHeading' => 'Tenha certeza que você quer realmente alterar!',
'GenderChangeFormText' => 'Você deseja mudar seu gênero para %s?',
'GenderChangeConfirm' => 'Você tem certeza que quer alterar seu gênero?',
'GenderChangeButton' => 'Sim, por favor.',
// - account/confirm
'AccountConfirmTitle' => 'Confirmar Conta',
'AccountConfirmUnban' => 'A conta foi confirmada e ativada.',
'AccountConfirmMessage' => 'Sua conta foi confirmada e ativada, agora você pode fazer login.',
// - account/confirmemail
'EmailConfirmTitle' => 'Confirmar E-mail',
'EmailConfirmFailed' => 'Tivemos um problema técnico durante sua alteração de email, por favor entre em contato com algum Administrador.',
'EmailConfirmChanged' => 'Seu e-mail foi alterado com sucesso!',
// - account/create
'AccountCreateTitle' => 'Criar Uma Conta',
'AccountConfirmBan' => 'Esperando ativação da conta: %s',
'AccountCreateEmailSent' => 'Um e-mail foi enviado contendo as informações de ativação da conta, por favor, cheque seu email e ative a sua conta para poder fazer login.',
'AccountCreateFailed' => 'Sua conta foi criada, mas infelizmente houve uma falha ao lhe enviar o email de confirmação devido a problemas técnicos. Por favor, procure algum GM ou Administrador para resolver o seu problema.',
'AccountCreated' => 'Parabéns! Você foi registrado com sucesso e você já está logado!',
'AccountCreateHeading' => 'Registrar',
'AccountCreateTerms' => 'Termos de Serviço',
'AccountCreateInfo' => 'Por favor, leia os nossos %s antes de criar uma conta, tenha certeza que você entendeu as regras para poder ter uma conta no nosso servidor.',
'AccountCreateInfo2' => 'Clicando em "Criar Minha Conta", você estará concordando com os nossos %s.',
'AccountCreateGenderInfo' => "O gênero que você escolheu irá afetar o gênero do seu personagem in-game!",
'AccountServerLabel' => 'Servidor',
'AccountUsernameLabel' => 'Seu Usuário',
'AccountPasswordLabel' => 'Sua Senha',
'AccountPassConfirmLabel' => 'Confirmar Senha',
'AccountEmailLabel' => 'E-mail',
'AccountGenderLabel' => 'Gênero',
'AccountBirthdateLabel' => 'Data de nascimento',
'AccountSecurityLabel' => 'Código de Segurança',
'AccountCreateButton' => 'Criar Minha Conta',
'AccountInvalidChars' => "Um nome de usuário pode conter somente estes caracteres: ' %s'",
'AccountRecaptchaKey' => 'Você precisa das chaves de Recaptcha, veja mais em config/applications.php (ReCaptchaPublicKey/ReCaptchaPrivateKey)',
'InvalidLoginServer' => 'Login inválido para servidor selecionado, por favor, tente novamente com um servidor válido.',
'InvalidLoginCredentials' => 'Login inválido credenciais, verifique se você digitou as informações corretas e tente novamente.',
'UnexpectedLoginError' => 'Ocorreu um erro inesperado, tente novamente ou reporte ao administrador.',
'CriticalLoginError' => 'Algo ruim aconteceu. Comunicar ao administrador o mais cedo possível.',
'UsernameAlreadyTaken' => "O nome de usuário que você escolheu já está em uso por outro usuário.",
'UsernameTooShort' => sprintf('Seu nome de usuário deve ser em torno de %d a %d caracteres longos.', Flux::config('MinUsernameLength'), Flux::config('MaxUsernameLength')),
'UsernameTooLong' => sprintf('Seu nome de usuário deve ser em torno de %d a %d caracteres longos.', Flux::config('MinUsernameLength'), Flux::config('MaxUsernameLength')),
'PasswordContainsUser' => 'Sua senha não pode conter seu nome de usuário.',
'PasswordHasUsername' => 'Sua senha não deve conter seu nome de usuário.',
'PasswordTooShort' => 'Sua senha deve ser em torno de %d a %d caracteres longo.',
'PasswordTooLong' => 'Sua senha deve ser em torno de %d a %d caracteres longo.',
'PasswordsDoNotMatch' => "Suas senhas não coincidem, por favor, certifique-se de que você digitou corretamente.",
'PasswordNeedUpper' => 'Sua senha deve conter pelo menos %d letra(s) maiúscula.',
'PasswordNeedLower' => 'Sua senha deve conter pelo menos %d letra(s) minúsculas.',
'PasswordNeedNumber' => 'Sua senha deve conter pelo menos %d número(s).',
'PasswordNeedSymbol' => 'Sua senha deve conter pelo menos %d símbolo(s).',
'EmailAddressInUse' => "Você digitou o endereço de e-mail já está registrado para outra conta. Por favor use um endereço de e-mail diferente.",
'InvalidEmailAddress' => "O endereço de email que você digitou não está em um formato de endereço de email válido.",
'InvalidGender' => 'Sexo deve ser "M" ou "F"',
'InvalidServer' => "O servidor que você selecionou não existe.",
'InvalidSecurityCode' => 'Por favor introduza o código de segurança corretamente.',
'InvalidPassword' => 'A senha contém caracteres inválidos.',
'InvalidBirthdate' => 'Entrada de Data de nascimento inválida.',
'CriticalRegisterError' => 'Algo ruim aconteceu. Comunicar ao administrador o mais cedo possível.',
// - account/edit
'AccountEditTitle' => 'Modificar Conta',
'AccountEditTitle2' => 'Modificando Minha Conta',
'AccountEditTitle3' => 'Modificando Conta (%s)',
'CannotModifyOwnGroupID' => 'Você não pode modificar seu próprio ID do grupo da conta.',
'CannotModifyAnyGroupID' => 'Você não pode modificar IDs do grupo de contas.',
'CannotModifyGroupIDHigh' => 'Você não pode definir um ID do grupo de conta para ser maior do que o seu próprio.',
'InvalidGroupID' => 'ID do grupo inválido.',
'CannotModifyBalance' => 'Você não pode mudar o balanço da conta.',
'InvalidLastLoginDate' => 'Última data e hora de login inválidos.',
'AccountModified' => 'Conta modificada com sucesso.',
'AccountEditHeading' => 'Modificar Conta',
'AccountEditButton' => 'Modificar Conta',
'AccountEditNotFound' => 'Conta não encontrada.',
// - account/index
'AccountIndexTitle' => 'Listar Contas',
'AccountIndexHeading' => 'Contas',
'LoginBetweenLabel' => 'Login Entre',
'BirthdateBetweenLabel' => 'Data de nascimento entre',
'AccountIndexNotFound' => 'Conta não encontrada.',
// - account/login
'LoginTitle' => 'Login',
'LoginHeading' => 'Login',
'LoginButton' => 'Login',
'LoginPageMakeAccount' => 'Você não possui uma conta? Faça uma agora!',
'TemporarilyBanned' => 'Sua conta foi banida temporariamente.',
'PermanentlyBanned' => 'Sua conta foi banida permanentemente.',
'IpBanned' => 'O seu IP foi banido.',
'PendingConfirmation' => 'Sua conta está esperando confirmação de e-mail.',
// - account/logout
'LogoutTitle' => 'Sair',
'LogoutHeading' => 'Sair',
'LogoutInfo' => 'Você saiu da sua conta.',
'LogoutInfo2' => 'Aguarde um momento enquanto você está sendo redirecionado…',
// - account/resend
'ResendTitle' => 'Re-enviar E-mail de Confirmação',
'ResendEnterUsername' => 'Por favor, insira o seu Usuário.',
'ResendEnterEmail' => 'Por favor, insira o seu E-mail.',
'ResendFailed' => 'Falha ao re-enviar código de confirmação.',
'ResendEmailSent' => 'Seu código de confirmação foi enviado, olhe a sua caixa de entrada para proceder com a ativação da sua conta.',
'ResendHeading' => 'Re-enviar E-mail de Confirmação',
'ResendInfo' => 'Por favor, insira o Usuário e E-mail que você utilizou durante o registro da sua conta para podermos re-enviar o seu e-mail de confirmação.',
'ResendServerLabel' => 'Servidor Registrado',
'ResendAccountLabel' => 'Usuário',
'ResendEmailLabel' => 'E-mail',
'ResendServerInfo' => 'Este é o servidor que a conta está registrada.',
'ResendAccountInfo' => 'Este é o usuário que você registrou.',
'ResendEmailInfo' => 'Este é o e-mail que você utilizou no registro da conta acima.',
'ResendButton' => 'Re-enviar E-mail de Confirmação',
// - account/resetpass
'ResetPassTitle' => 'Redefinir Senha',
'ResetPassEnterAccount' => 'Por favor, digite o seu Usuário.',
'ResetPassEnterEmail' => 'Por favor, digite o seu e-mail.',
'ResetPassDisallowed' => 'Recuperação de senha não pode ser usada para esta conta.',
'ResetPassFailed' => 'Falha ao enviar o email de redefinição de senha.',
'ResetPassEmailSent' => 'Um e-mail foi enviado para você com os detalhes de como proceder para redefinir sua senha.',
'ResetPassInfo' => 'Se você perder a sua senha, você pode redefiní-la digitando apenas o email que você cadastrou na sua conta.',
'ResetPassInfo2' => 'Uma mensagem será enviada ao email digitado contendo um link para você poder redefinir a sua senha, por isso é necessário que você possua um e-mail válido.',
'ResetPassServerLabel' => 'Servidor Registrado',
'ResetPassAccountLabel' => 'Usuário',
'ResetPassEmailLabel' => 'E-mail',
'ResetPassServerInfo' => 'Este é o servidor que a conta está registrada.',
'ResetPassAccountInfo' => 'Este é o usuário que você registrou.',
'ResetPassEmailInfo' => 'Este é o e-mail que você utilizou no registro da conta acima.',
'ResetPassButton' => 'Enviar E-mail para Redefinição de Senha',
// - account/resetpw
'ResetPwTitle' => 'Redefinir Senha',
'ResetPwFailed' => 'Falha ao redefinir senha, tente novamente mais tarde.',
'ResetPwDone' => 'Sua senha foi redefinida e um e-mail contendo a sua nova senha foi enviada para você.',
'ResetPwDone2' => 'A sua senha foi redefinida, mas houve uma falha ao lhe enviar um e-mail contendo sua nova senha. Por favor, tente redefiní-la novamente para resolver esse problema.',
// - account/transfer
'TransferTitle' => 'Transferir Créditos de Doação',
'TransferGreaterThanOne' => 'Você só pode transferir 1 ou mais créditos.',
'TransferEnterCharName' => 'Você deve digitar o nome do personagem que vai receber os créditos.',
'TransferNoCharExists' => "O personagem '%s' não existe. Tenha certeza que você digitou o nome correto.",
'TransferNoBalance' => 'Você não tem saldo o suficiente para fazer uma transferência.',
'TransferUnexpectedError' => 'Erro inesperado ocorreu.',
'TransferSuccessful' => 'Os créditos foram transferidos!',
'TransferHeading' => 'Transferir Créditos de Doação',
'TransferSubHeading' => 'Os créditos serão transferidos para um personagem no servidor %s.',
'TransferInfo' => 'Você possui %s crédito(s).',
'TransferInfo2' => 'Insira a quantidade que você quer transferir e o nome do personagem pertencente a conta que você quer enviar os créditos.',
'TransferAmountLabel' => 'Quantidade de Créditos',
'TransferCharNameLabel' => 'Nome do Personagem',
'TransferAmountInfo' => 'Esta é a quantidade de crédito que você deseja enviar.',
'TransferCharNameInfo' => 'Este é o nome do personagem que está recebendo os créditos.',
'TransferConfirm' => 'Tem certeza que quer transferir?',
'TransferButton' => 'Transferir',
'TransferNoCredits' => 'Você não possui créditos disponíveis na sua conta.',
// - account/view
// * account/view submenus
'ModifyAccountLink' => 'Modificar Conta',
'AccountViewTitle' => 'Ver Conta',
'AccountViewTitle2' => 'Vendo Conta (%s)',
'AccountViewTitle3' => 'Vendo Minha Conta',
'AccountTempBanFailed' => 'Falha ao banir temporariamente a conta.',
'AccountPermBanFailed' => 'Falha ao banir permanentemente a conta.',
'AccountTempBanUnauth' => 'Você não está autorizado a banir temporariamente essa conta.',
'AccountPermBanUnauth' => 'Você não está autorizado a banir permanentemente essa conta.',
'AccountLiftTempBan' => 'Conta foi desbanida.',
'AccountLiftPermBan' => 'Conta foi desbanida.',
'AccountLiftBanUnauth' => "Você não está autorizado a desbanir essa conta.",
'AccountViewHeading' => 'Vendo Conta',
'AccountViewDonateLink' => '(Doar!)',
'AccountViewTempBanLabel' => 'Banir Temporariamente',
'AccountViewPermBanLabel' => 'Banir Permanentemente',
'AccountViewUnbanLabel' => 'Remover Banimento',
'AccountBanReasonLabel' => 'Razão:',
'AccountBanUntilLabel' => 'Banido Até:',
'AccountTempBanButton' => 'Banir Conta',
'AccountPermBanButton' => 'Banir Conta Permanentemente',
'AccountTempUnbanButton' => 'Removeer Banimento Temporário',
'AccountPermUnbanButton' => 'Removeer Banimento Permanente',
'AccountBanConfirm' => 'Tem certeza?',
'AccountBanLogSubHeading' => 'Log de Banimento para %s (mais novo para mais antigo)',
'BanLogBanTypeLabel' => 'Tipo do Banimento',
'BanLogBanDateLabel' => 'Data do Banimento',
'BanLogBanReasonLabel' => 'Razão do Banimento',
'BanLogBannedByLabel' => 'Banido Por',
'BanLogBannedByCP' => 'Painel de Controle',
'BanTypeUnbanned' => 'Desbanido',
'BanTypePermBanned' => 'Banido Permanentemente',
'BanTypeTempBanned' => 'Banido Temporariamente',
'AccountViewCharSubHead' => 'Personagens em %s',
'AccountViewSlotLabel' => 'Slot',
'AccountViewCharLabel' => 'Nome do Personagem',
'AccountViewClassLabel' => 'Classe',
'AccountViewLvlLabel' => 'Nível de Base',
'AccountViewJlvlLabel' => 'Nível de Job',
'AccountViewZenyLabel' => 'Zeny',
'AccountViewGuildLabel' => 'Clã',
'AccountViewStatusLabel' => 'Status',
'AccountViewPrefsLabel' => 'Preferências',
'CharModifyPrefsLink' => 'Modificar Preferências',
'AccountViewNoChars' => 'Essa conta não possui personagens em %s.',
'AccountViewStorage' => 'Storage de %s',
'AccountViewStorageCount' => '%s tem %s item(s) no storage.',
'AccountViewNoStorage' => 'Não há itens no storage dessa conta.',
'AccountViewNotFound' => "Registros indicam que a conta que você está tentando ver não existe.",
// - account/xferlog
'XferLogTitle' => 'Histórico de Transferência de Crédito',
'XferLogHeading' => 'Histórico de Transferência de Crédito',
'XferLogReceivedSubHead' => 'Transferências: Recebidas',
'XferLogSentSubHead' => 'Transferências: Enviadas',
'XferLogCreditsLabel' => 'Créditos',
'XferLogFromLabel' => 'Do E-mail',
'XferLogDateLabel' => 'Data da Transferência',
'XferLogCharNameLabel' => 'Para o Personagem',
'XferLogNotReceived' => 'Você não recebeu nenhuma transferência de crédito.',
'XferLogNotSent' => 'Você não fez nenhuma transferência de crédito.',
// Module: character
// - character/changeslot
// - character/index
// - character/mapstats
// - character/online
// - character/prefs
// - character/resetlook
'CantResetLookWhenOnline' => 'Não pode redefinir aparência enquanto %s estiver online.',
'ResetLookSuccessful' => "A aparência de %s foi redefinida!",
'ResetLookFailed' => "Falha ao redefinir a aparência de %s",
// - character/resetpos
'CantResetPosWhenOnline' => 'Não pode redefinir posição enquanto %s estiver online.',
'CantResetFromCurrentMap' => "Você não pode redefinir a posição de %s estando no mapa atual.",
'ResetPositionSuccessful' => "A posição de %s foi redefinida!",
'ResetPositionFailed' => "Falha ao redefinir a posição de %s.",
// - character/view
// - character/divorce
'DivorceTitle' => 'Divórcio',
'DivorceHeading' => 'Divórcio',
'DivorceNotMarried' => '%s não é casado.',
'DivorceInvalidPartner' => 'ID de parceiro inválido.',
'DivorceInvalidChild' => 'ID de filho inválido.',
'DivorceMustBeOffline' => 'Ambos s% e seu/sua parceiro(a) deve estar off-line.',
'DivorceMustBeOffline2' => '%s, seu/sua parceiro(a) e seu filho deve estar off-line.',
'DivorceText1' => "Você tem certeza que quer se divorciar de %s seu/sua parceiro(a)?",
'DivorceText2' => 'Se %s tem um filho, o filho também vai ser órfão.',
'DivorceText3' => 'Anéis de casamento também serão excluídos.',
'DivorceButton' => 'Sim, fazê-lo por favor.',
'DivorceSuccessful' => '%s já se divorciou!',
// Module: cplog
// - cplog/index.php
// - cplog/login.php
// - cplog/paypal.php
// - cplog/resetpass.php
// - cplog/txnview.php
// Module: donate
// - donate/complete
// - donate/history
// - donate/index
// - donate/trusted
// Module: errors
// - errors/missing_action
'MissingActionTitle' => 'Ação Inexistente',
'MissingActionHeading' => 'Ação Inexistente!',
'MissingActionModLabel' => 'Módulo:',
'MissingActionActLabel' => 'Ação:',
'MissingActionReqLabel' => 'URL requerida:',
'MissingActionLocLabel' => 'Localização do arquivo de sistema:',
// - errors/missing_view
'MissingViewTitle' => 'Faltando Página', // Precisa de tradução exata, original: "Missing View"
'MissingViewHeading' => 'Faltando Página!', // Precisa de tradução exata, original: "Missing View!"
'MissingViewModLabel' => 'Módulo:',
'MissingViewActLabel' => 'Ação:',
'MissingViewReqLabel' => 'URL requerida:',
'MissingViewLocLabel' => 'Localização do arquivo de sistema:',
// Module: guild
// - guild/export
// - guild/index
// - guild/view
// Module: history
// - history/cplogin
'HistoryCpLoginTitle' => 'Logins no Painel de Controle',
'HistoryCpLoginHeading' => 'Logins no Painel de Controle',
'HistoryLoginDateLabel' => 'Data/Hora do Login',
'HistoryIpAddrLabel' => 'Endereço de IP',
'HistoryErrorCodeLabel' => 'Código de Erro',
'HistoryNoCpLogins' => 'Não foram feitos logins no Painel de Controle ainda.',
// -history/emailchange
'HistoryEmailTitle' => 'Mudanças de E-Mail',
'HistoryEmailHeading' => 'Mudanças de E-Mail',
'HistoryEmailRequestDate' => 'Data/Hora da requisição',
'HistoryEmailRequestIp' => 'IP que fez a requisição',
'HistoryEmailOldAddress' => 'E-Mail Antigo',
'HistoryEmailNewAddress' => 'E-Mail Novo',
'HistoryEmailChangeDate' => 'Data da Mudança',
'HistoryEmailChangeIp' => 'IP que fez a mudança',
'HistoryNoEmailChanges' => 'Nenhuma tentativa de mudança de email encontrada.',
// - history/gamelogin
'HistoryGameLoginTitle' => 'Logins No Jogo',
'HistoryGameLoginHeading' => 'Logins No Jogo',
'HistoryRepsCodeLabel' => 'Resposta',
'HistoryLogMessageLabel' => 'Mensagem de Log',
'HistoryNoGameLogins' => 'Não foram feitos logins no jogo ainda.',
// - history/index
'HistoryIndexTitle' => 'Histórico da Minha Conta',
'HistoryIndexHeading' => 'Histórico da Minha Conta',
'HistoryIndexInfo' => 'Aqui você pode ver a atividade passada da sua conta.',
'HistoryIndexInfo2' => 'Por favor, selecione a página desejada no menu.',
// - history/passchange
'HistoryPassChangeTitle' => 'Alterações de senha',
'HistoryPassChangeHeading' => 'Alterações de senha',
'HistoryPassChangeChangeDate' => 'Alterar data',
'HistoryPassChangeChangeIp' => 'Mudar IP',
'HistoryNoPassChanges' => 'Nenhuma alteração de senha encontrada.',
// -history/passreset
'HistoryPassResetTitle' => 'Redefinições de senha',
'HistoryPassResetHeading' => 'Redefinições de senha',
'HistoryPassResetRequestDate' => 'Solicitação de Data/Hora',
'HistoryPassResetRequestIp' => 'Solicitar IP',
'HistoryPassResetResetDate' => 'Redefinir a data',
'HistoryPassResetResetIp' => 'Redefinir o IP',
'HistoryNoPassResets' => 'Nenhuma senha Redefinida encontradas.',
// Module: ipban
// - ipban/add
'IpbanAddTitle' => 'Banir IP',
'IpbanEnterIpPattern' => 'Por favor, digite um IP ou um padrão de IP.',
'IpbanInvalidPattern' => 'IP ou padrão inválido.',
'IpbanWhitelistedPattern' => 'Esse padrão é lista branca e não pode ser bloqueado.',
'IpbanEnterReason' => 'Digite a razão do banimento para o IP.',
'IpbanSelectUnbanDate' => 'Data de desbanimento necessária.',
'IpbanFutureDate' => 'Data de desbanimento deve ser uma data futura.',
'IpbanAlreadyBanned' => 'O IP (%s) já consta como banido.',
'IpbanPatternBanned' => "O IP ou o padrão '%s' foi banido.",
'IpbanAddFailed' => 'Falha ao banir IP.',
'IpbanAddHeading' => 'Banir IP',
'IpbanIpAddressLabel' => 'Endereço de IP',
'IpbanReasonLabel' => 'Razão',
'IpbanUnbanDateLabel' => 'Data de desbanimento',
'IpbanIpAddressInfo' => 'Você pode especificar um padrão de IP como 218.139.*.*',
'IpbanAddButton' => 'Banir IP',
// - ipban/edit
'IpbanEditTitle' => 'Modificar Banimento de IP',
'IpbanEnterEditReason' => 'Por favor, digite uma razão para a modificação de Banimento de IP .',
'IpbanEditFailed' => 'Falha ao modificar Banimento de IP.',
'IpbanEditHeading' => 'Modificar Banimento de IP',
'IpbanEditReasonLabel' => 'Editar a razão',
'IpbanEditButton' => 'Modificar Banimento de IP',
// - ipban/index
'IpbanListTitle' => 'Lista de IP Banidos',
'IpbanListHeading' => 'Lista de IP Banidos',
'IpbanBannedIpLabel' => 'IP Banido',
'IpbanBanDateLabel' => 'Data do Banimento',
'IpbanBanReasonLabel' => 'Razão',
'IpbanBanExpireLabel' => 'Data de expiração do Banimento',
'IpbanModifyLink' => 'Modificar',
'IpbanRemoveLink' => 'Remover',
'IpbanUnbanButton' => 'Desbanir Selecionados',
'IpbanListNoBans' => 'Atualmente não há IPs banidos.',
// - ipban/remove
'IpbanRemoveTitle' => 'Remover Banimento de IP',
'IpbanEnterRemoveReason' => 'Por favor, digite uma razão para a remoção do Banimento de IP.',
'IpbanNotBanned' => 'Nenhum IP correspondente (%s) atualmente é proibido.',
'IpbanPatternUnbanned' => "O endereço IP/padrão '%s' foi banido.",
'IpbanRemoveFailed' => 'Falha ao remover a Banimento de IP.',
'IpbanRemoveHeading' => 'Remover Banimento de IP',
'IpbanRemoveReasonLabel' => 'Razão do desbanimento',
'IpbanRemoveButton' => 'Remover Banimento de IP',
// - ipban/unban
'IpbanNothingToUnban' => 'Nada para desbanir.',
'IpbanEnterUnbanReason' => 'Por favor, digite uma razão para desbanir o(s) IP(s).',
'IpbanUnbanned' => 'IP(s) selecionado(s) desbanido(s)!',
'IpbanUnbanFailed' => 'Falha ao desbanir %d IP(s) especificado(s)!',
// Module: item
// - item/add
// - item/copy
// - item/edit
// - item/index
// - item/view
// Module: itemshop
// - itemshop/add
// - itemshop/delete
// - itemshop/edit
// - itemshop/imagedel
// Module: logdata
// - logdata/chat
// - logdata/command
'CommandLogTitle' => 'Lista de comandos',
'CommandLogHeading' => 'Log de comandos',
'CommandLogNotFound' => 'Nenhum comando registrado',
'CommandLogDateLabel' => 'Comando Data/Hora',
'CommandLogAccountIdLabel'=> 'ID da conta',
'CommandLogCharIdLabel' => 'ID do char',
'CommandLogCharNameLabel' => 'Personagem',
'CommandLogCommandLabel' => 'Comando',
'CommandLogMapLabel' => 'Mapa',
// - logdata/index
// - logdata/login
// - logdata/pick
'PickLogTitle' => 'Lista de log de itens',
'PickLogHeading' => 'Log de itens',
'PickLogNotFound' => 'Nenhum item registrado',
'PickLogDateLabel' => 'Data/Hora',
'PickLogCharacterLabel' => 'Personagem',
'PickLogTypeLabel' => 'Tipo',
'PickLogItemLabel' => 'Nome do item',
'PickLogAmountLabel' => 'Quantidade',
'PickLogRefineLabel' => 'Refino',
'PickLogCard0Label' => 'Carta 1',
'PickLogCard1Label' => 'Carta 2',
'PickLogCard2Label' => 'Carta 3',
'PickLogCard3Label' => 'Carta 4',
'PickLogMapLabel' => 'Mapa',
// - logdata/zeny
'ZenyLogTitle' => 'Lista de log de zeny',
'ZenyLogHeading' => 'Log de zeny',
'ZenyLogNotFound' => 'Não há registro de logs de zeny',
'ZenyLogDateLabel' => 'Data/Hora',
'ZenyLogCharacterLabel' => 'Personagem',
'ZenyLogSourceLabel' => 'ID do Personagem',
'ZenyLogTypeLabel' => 'Tipo',
'ZenyLogAmountLabel' => 'Valor',
'ZenyLogMapLabel' => 'Mapa',
// Module: mail
// - mail/index
'MailerTitle' => 'Formulário de Email',
'MailerHeading' => 'Formulário de Email',
'MailerEnterToAddress' => 'Digite o destinatário.',
'MailerEnterSubject' => 'Digite o assunto.',
'MailerEnterBodyText' => 'Digite o corpo de texto.',
'MailerEmailHasBeenSent' => 'Seu e-mail foi enviado com sucesso para %s.',
'MailerFailedToSend' => 'O sistema de e-mail falhou ao enviar a mensagem. Isso pode ser configuração.',
'MailerInfo' => 'Você pode usar o formulário abaixo para enviar e-mails usando o Painel de Controle.',
'MailerFromLabel' => 'De',
'MailerToLabel' => 'Para',
'MailerSubjectLabel' => 'Assunto',
'MailerBodyLabel' => 'Corpo da Mensagem',
// Module: main
// - main/index
'MainPageHeading' => 'Flux Control Panel',
'MainPageInfo' => "Se você está vendo esta página, é porque você instalou o Flux Control Panel com sucesso!",
'MainPageInfo2' => "Você gostaria de mudar esta página? Então, aqui você pode mudar:",
'MainPageStep1' => 'Abra o arquivo "%s" no seu editor de texto.',
'MainPageStep2' => 'E edite o arquivo como você quiser!',
'MainPageThanks' => 'Obrigado por usar o Flux!',
// - main/pagenotfound
'PageNotFoundTitle' => '404 Página Não Encontrada',
'PageNotFoundHeading' => 'Página Não Encontrada',
'PageNotFoundInfo' => 'A página que você solicitou não foi encontrada. Por favor, verifique se o endereço está correto e tente novamente.',
// - main/preprocess
'DisallowedDuringWoE' => 'A página que você solicitou não está disponível durante a GdE.',
// Module: monster
// - monster/index
// - monster/view
// Module: purchase
// - purchase/add
// - purchase/cart
// - purchase/checkout
// - purchase/clear
// - purchase/index
// - purchase/pending
// - purchase/remove
// Module: ranking
// - ranking/character
// - ranking/guild
// - ranking/zeny
// Module: server
// - server/info
'ServerInfoTitle' => 'Informação do Servidor',
'ServerInfoHeading' => 'Informação do Servidor',
'ServerInfoText' => "Aqui você encontra várias informações sobre o servidor.",
'ServerInfoSubHeading' => 'Informação para %s',
'ServerInfoSubHeading2' => 'Informação de Classe para %s',
'ServerInfoAccountLabel' => 'Contas',
'ServerInfoCharLabel' => 'Personagens',
'ServerInfoGuildLabel' => 'Clãs',
'ServerInfoPartyLabel' => 'Grupos',
'ServerInfoZenyLabel' => 'Zeny',
// - server/status
'ServerStatusTitle' => 'Status do Servidor',
'ServerStatusHeading' => 'Status do Servidor',
'ServerStatusInfo' => "Entendendo o status de Online e Offline de cada servidor pode lhe ajudar a entender como relatar o seu problema. Por exemplo, se o login server estiver offline, isso quer dizer que não é possível fazer o login no jogo. O character server e o map server são necessários para você entrar na escolha de seu personagem e no mapa do jogo depois que você faz o login.",
'ServerStatusServerLabel' => 'Servidor',
'ServerStatusLoginLabel' => 'Login Server',
'ServerStatusCharLabel' => 'Character Server',
'ServerStatusMapLabel' => 'Map Server',
'ServerStatusOnlineLabel' => 'Jogadores Online',
'ServerStatusPeakLabel' => 'Pico de jogador',
// Module: service
// - service/tos
'TermsTitle' => 'Termos de Serviço',
'TermsHeading' => 'Termos de Serviço',
'TermsInfo' => 'Por favor, leia tudo antes de criar a sua conta!',
'TermsInfo2' => "PARA O ADMINISTRADOR DO PAINEL DE CONTROLE: Você pode adicionar os Termos de Serviço diretamente neste arquivo. A localização do arquivo é: %s",
// Module: unauthorized
// - unauthorized/index
'UnauthorizedTitle' => 'Não Autorizado',
'UnauthorizedHeading' => 'Não Autorizado',
'UnauthorizedInfo' => 'Você não está autrizado a ver essa página. Redirecionando…',
// Module: woe
// - woe/index
'WoeTitle' => 'Horários da Guerra do Emperium',
'WoeHeading' => 'Horários da Guerra do Emperium',
'WoeInfo' => "Aqui estão os horários da GdE para o %s. Esses horários estão sujeitos a alteração sem aviso prévio, portanto mantenha-se informado.",
'WoeServerTimeInfo' => 'A hora atual do servidor é:',
'WoeServerLabel' => 'Servidores',
'WoeTimesLabel' => 'Horários da Guerra do Emperium',
'WoeNotScheduledInfo' => 'Não há nenhuma Guerra do Emperium agendada.',
// Module: tasks
'TaskListHeader' => 'Lista de tarefas',
'TaskListHeaderCompleted' => 'Tarefas concluídas',
'TaskListAdd' => 'Adicionar nova tarefa',
'TaskListAdded' => 'Tarefa adicionada!',
'TaskListSub' => 'Lista de tarefas dos GM\'s!',
'TLNotAssigned' => 'Não atribuído',
'TLNoTasks' => 'Não há nenhuma tarefa.!',
'TLNoMine' => 'Você não tem tarefas atribuídas a você.',
'TLNoCompleted' => 'Não há tarefas concluídas ainda.',
'TLHeaderTasks' => 'Tarefas',
'TLHeaderOwner' => 'Atribuido à',
'TLHeaderPriority' => 'Prioridade',
'TLHeaderStatus' => 'Estado',
'TLHeaderCreated' => 'Criado',
'TLHeaderModified' => 'Modificado',
'TLHeaderResources' => 'Informações',
'TLHeaderBody' => 'Caixa de Mensagem',
'TLPriority1' => 'Urgente',
'TLPriority2' => 'Alta',
'TLPriority3' => 'Baixa',
'TLStatus0' => 'Nova Tarefa',
'TLStatus1' => 'Em andamento',
'TLStatus2' => 'Aguardando Implementação',
'TLStatus5' => 'completo',
'TLHuh' => 'Você não deve ser capaz de ver isso!',
// Module: contactform
'CFTitleSubmit' => 'Contate-nos',
// Module: logdata/harmony
'HARTitle' => 'Harmony Logs',
'HARSearchLink' => 'Pesquisar...',
'HARDateBetween' => 'Data entre',
'HARIPAddress' => 'Endereço IP',
'HARCharacter' => 'Personagem',
'HARAccountID' => 'ID da conta',
'HARNoData' => 'Nenhum dado foi encontrado.',
'HARGoback' => 'Voltar',
// Module: News and Pages
'XCMSNewsHeader' => 'Announcements',
'XCMSPageHeader' => 'Content Management System',
'XCMSPageText' => 'This FluxCP addon enables server admins and staff to create pages within their website with no prior flux/coding knowledge. The built-in news system can also be swapped out for an rss feed by modifying the addon settings.',
'XCMSNewsTitleError' => 'News title is required!',
'XCMSNewsBodyError' => 'News body is required!',
'XCMSPageTitleError' => 'Page Title is required!',
'XCMSPageBodyError' => 'Page body is required!',
'XCMSPagePathError' => 'Page path is required!',
'XCMSNewsAdded' => 'News added to system',
'XCMSPagesAdded' => 'Your new page has been added',
'XCMSNewsUpdated' => 'News updated',
'XCMSPageUpdated' => 'Your page has been updated',
'XCMSNewsAddTitle' => 'Add a news item',
'XCMSPageAddTitle' => 'Add a new page',
'XCMSNewsEditTitle' => 'Edit news',
'XCMSPageEditTitle' => 'Edit page',
'XCMSNewsNotFound' => 'News not found!',
'XCMSPageNotFound' => 'Page not found!',
'XCMSNewsDeleted' => 'News deleted',
'XCMSPageDeleted' => 'Your page has been deleted',
'XCMSNewsEmpty' => 'No news articles have been found. Are you using the correct News Type? (XCMSNewsType setting)',
'XCMSNewsRSSNotFound' => 'RSS feed can\'t be found. Make sure the XCMSNewsRSS setting is correct, or switch XCMSNewsType to 1 to use built-in news system!',
'XCMSNewsTXTNotFound' => 'File import does not work in this version. The addon.php file actually tells you this! Switch XCMSNewsType to 1 to use built-in news system, or 2 to use RSS import feature!',
'XCMSNewsFBNotFound' => 'Facebook feed not found!',
'XCMSPageEmpty' => 'No page added',
'XCMSNewsLink' => 'read more...',
'XCMSEdit' => 'Edit',
'XCMSDelete' => 'Delete',
'XCMSNewsTitleLabel' => 'News Title',
'XCMSNewsBodyLabel' => 'News Body',
'XCMSNewsLinkLabel' => 'News Link',
'XCMSNewsAuthorLabel' => 'News Author',
'XCMSPageTitleLabel' => 'Page Title',
'XCMSPageBodyLabel' => 'Page Body',
'XCMSPagePathLabel' => 'Page Path',
'XCMSCreatedLabel' => 'Date Created',
'XCMSModifiedLabel' => 'Date Modified',
'XCMSActionLabel' => 'Action',
'XCMSConfirmDelete' => 'Are you sure you want to delete?',
'XCMSPageCreate' => 'Create now?',
'XCMSOptionalLabel' => '(Optional)',
'XCMSRequiredLabel' => '(Required)',
// Module: servicedesk
'SDHeader' => 'Serviço de tickets',
'SDCreateNew' => 'Criar um novo ticket',
'SDWelcomeText' => 'Bem vindo ao serviço de Ticket',
'SDNoTickets' => 'Você ainda não criou nenhum ticket.',
'SDNoBlankResponse' => 'É necessario digitar uma resposta para enviar.',
'SDNoCatsAvailable' => 'Nenhuma categoria ativa',
'SDNoOpenTickets' => 'Não existem tickets abertos.',
'SDNoInactiveTickets' => 'Não existem tickets inativos.',
'SDNoClosedTickets' => 'Não existem tickets fechados.',
'SDNoCats' => 'Não existem categorias.',
'SDHuh' => 'Você não deveria estar aqui o.O',
'SDPointerChatLog' => 'Nós recomendamos que você cole o log aqui pastebin.com e nos envie o erro.',
'SDPointerScreenShot' => 'Nos envie links de imagens para serem utilizados como provas',
'SDPointerVideoLink' => 'Recomendamos que envie para o youtube e coloque o link aqui',
'SDHeaderID' => 'Ticket #',
'SDHeaderSubject' => 'Resposta',
'SDHeaderCategory' => 'Categoria',
'SDHeaderStatus' => 'Status atual',
'SDHeaderLastAuthor' => 'Última resposta',
'SDHeaderTimestamp' => 'Criado',
'SDHeaderAccount' => 'Conta',
'SDHeaderTeam' => 'Equipe',
'SDH3ActiveTickets' => 'Tickets ativos',
'SDH3InActiveTickets' => 'Tickets inativos',
'SDH3ClosedTickets' => 'Tickets fechados',
'SDH3CurrentCat' => 'Categorias',
'SDH3CreateCat' => 'Criar nova categoria',
'SDH3StaffList' => 'Current Staff Settings',
'SDH3StaffCreate' => 'Add Staff Settings',
'SDReOpenPlayer' => 'Ticket reaberto pelo jogador',
'SDReOpenStaff' => 'Ticket reaberto pela equipe',
'SDRespTable1' => 'Responder e retornar para o Ticket',
'SDRespTable2' => 'Responder e retornar para a lista',
'SDRespTable3' => 'Responder e fechar o Ticket',
'SDRespTable4' => 'Responder e enviar para outro GM',
'SDRespTable5' => 'Fechar Ticket',
'SDRespTable6' => 'Responder e reabrir o Ticket',
'SDGroup1' => 'GM',
'SDGroup2' => 'GM Chefe',
'SDGroup3' => 'Admin',
'SDLinkOpenNew' => 'Abrir novo ticket',
// Module: webcommands
'WCTitleLabel' => 'Web Commands',
//Menus
'NewsLabel' => 'Novidades',
'MyAccountLabel' => 'Minha Conta',
'HistoryLabel' => 'Logs da conta',
'ServiceDeskLabel' => 'Abrir ticket',
'ServerInfoLabel' => 'Informações do servidor',
'ServerStatusLabel' => 'Status do servidor',
'WoeHoursLabel' => 'Horário da WoE',
'CastlesLabel' => 'Castelos',
'WhosOnlineLabel' => 'Quem está online',
'MapStatisticsLabel' => 'Estatisticas de mapa',
'RankingInfoLabel' => 'Informações dos Rankings',
'VendingInfoLabel' => 'Lojas abertas',
'JoinUsInFacebookLabel' => 'Pagina no facebook',
'MainMenuLabel' => 'Main Menu',
'ForumLabel' => 'Forum',
'AccountLabel' => 'Account',
'CharacterLabel' => 'Character',
'CPLogsLabel' => 'CP Logs',
'FluxAdminLabel' => 'Flux Admin',
'PagesLabel' => 'Pages',
'IPBanListLabel' => 'IP Ban List',
'GuildsLabel' => 'Guilds',
'rALogsLabel' => 'rA Logs',
'SendMailLabel' => 'Send Mail',
'ReInstallLabel' => 'Re-Install',
'TaskListLabel' => 'Task List',
'DonationsLabel' => 'Donations',
'InformationLabel' => 'Information',
'DatabaseLabel' => 'Database',
'SocialLabel' => 'Social',
'HomeLabel' => 'Home',
'DownloadsLabel' => 'Downloads',
'RulesLabel' => 'Rules',
'ContactUsLabel' => 'Contact Us',
'PurchaseLabel' => 'Purchase',
'DonateLabel' => 'Donate',
'MapStaticsLabel' => 'Map Statics',
'ItemDatabaseLabel' => 'Item Database',
'MobDatabaseLabel' => 'Mob Database',
'RateUsOnRMSLabel' => 'Rate us on RMS!',
'AccountEmailLabel2' => 'Confirm E-mail Address',
'InvalidEmailconf' => 'E-mail addresses do not match.',
'InvalidVIPTime' => 'Invalid VIP Time.',
'VIPTimeDateLabel' => 'VIP Until',
'VIPStateLabel' => 'VIP Status',
'CashLogTitle' => 'List CashPoints Log',
'CashLogHeading' => 'CashPoint Log',
'CashLogNotFound' => 'No cash logs found.',
'CashLogDateLabel' => 'Date/Time',
'CashLogCharacterLabel' => 'Character',
'CashLogTypeLabel' => 'Type',
'CashLogCashTypeLabel' => 'Cash Type',
'CashLogAmountLabel' => 'Amount',
'CashLogMapLabel' => 'Map',
'BranchLogTitle' => 'List Branch Log',
'BranchLogHeading' => 'Branch Log',
'BranchLogNotFound' => 'No branch logs found.',
'BranchLogIDLabel' => 'Branch Log ID',
'BranchLogDateLabel' => 'Date / Time',
'BranchLogAccountIDLabel' => 'Account ID',
'BranchLogCharIDLabel' => 'Char ID',
'BranchLogCharNameLabel' => 'Char Name',
'BranchLogMapLabel' => 'Map',
'CharLogTitle' => 'List Character Log',
'CharLogHeading' => 'Character Log',
'CharLogNotFound' => 'No character logs found.',
'CharLogDateLabel' => 'Date / Time',
'CharLogMsgLabel' => 'Action',
'CharLogAccountIDLabel' => 'Account ID',
'CharLogCharNumLabel' => 'Character slot',
'CharLogCharNameLabel' => 'Character Name',
'InterLogTitle' => 'List of Interactions Log',
'InterLogHeading' => 'Interactions Log',
'InterLogNotFound' => 'No Interactions logs found.',
'InterLogDateLabel' => 'Date / Time',
'InterLogLabel' => 'Interactions Log',
'MVPLogTitle' => 'List MVP Log',
'MVPLogHeading' => 'MVP Log',
'MVPLogNotFound' => 'No MVP logs found.',
'MVPLogIDLabel' => 'MVP Log ID',
'MVPLogDateLabel' => 'Date / Time',
'MVPLogCharacterLabel' => 'Character ID',
'MVPLogMonsterLabel' => 'MVP Monster',
'MVPLogPrizeLabel' => 'MVP Prize',
'MVPLogExpLabel' => 'MVP Experience',
'MVPLogMapLabel' => 'Map',
'NPCLogTitle' => 'List NPC Log',
'NPCLogHeading' => 'NPC Log',
'NPCLogNotFound' => 'No npc logs found.',
'NPCLogIDLabel' => 'NPC ID',
'NPCLogDateLabel' => 'Date / Time',
'NPCLogAccountIDLabel' => 'Account ID',
'NPCLogCharIDLabel' => 'Character ID',
'NPCLogCharNameLabel' => 'Character Name',
'NPCLogMapLabel' => 'Map',
'NPCLogMsgLabel' => 'Message',
'MailerSelectTemplateLabel' => 'Select Template',
'MainPageWelcome' => 'Welcome to %s!',
'CMSNewsHeader' => 'Announcements',
'CMSPageHeader' => 'Content Management System',
'CMSPageText' => 'This module enables server admins and staff to create pages within their website with no prior flux/coding knowledge. The built-in news system can also be swapped out for an rss feed by modifying the application settings.',
'CMSNewsTitleError' => 'News title is required!',
'CMSNewsBodyError' => 'News body is required!',
'CMSPageTitleError' => 'Page Title is required!',
'CMSPageBodyError' => 'Page body is required!',
'CMSPagePathError' => 'Page path is required!',
'CMSNewsAdded' => 'News added to system',
'CMSPagesAdded' => 'Your new page has been added',
'CMSNewsUpdated' => 'News updated',
'CMSPageUpdated' => 'Your page has been updated',
'CMSNewsAddTitle' => 'Add a news item',
'CMSPageAddTitle' => 'Add a new page',
'CMSNewsEditTitle' => 'Edit news',
'CMSPageEditTitle' => 'Edit page',
'CMSNewsNotFound' => 'News not found!',
'CMSPageNotFound' => 'Page not found!',
'CMSNewsDeleted' => 'News deleted',
'CMSPageDeleted' => 'Your page has been deleted',
'CMSNewsEmpty' => 'No news articles have been found. Are you using the correct News Type? (CMSNewsType setting)',
'CMSNewsRSSNotFound' => "RSS feed can't be found. Make sure the CMSNewsRSS setting is correct, or switch CMSNewsType to 1 to use built-in news system!",
'CMSPageEmpty' => 'No page added',
'CMSNewsLink' => 'read more...',
'CMSEdit' => 'Edit',
'CMSDelete' => 'Delete',
'CMSNewsTitleLabel' => 'News Title',
'CMSNewsBodyLabel' => 'News Body',
'CMSNewsLinkLabel' => 'News Link',
'CMSNewsAuthorLabel' => 'News Author',
'CMSPageTitleLabel' => 'Page Title',
'CMSPageBodyLabel' => 'Page Body',
'CMSPagePathLabel' => 'Page Path',
'CMSCreatedLabel' => 'Date Created',
'CMSModifiedLabel' => 'Date Modified',
'CMSActionLabel' => 'Action',
'CMSConfirmDeleteLabel' => 'Are you sure you want to delete?',
'CMSPageCreate' => 'Create now?',
'CMSOptionalLabel' => '(Optional)',
'CMSRequiredLabel' => '(Required)',
'CMSCreateLabel' => 'Add News',
'SDRespTable7' => 'Resolve Ticket and Credit Account',
);
?>
================================================
FILE: lib/Flux/Addon.php
================================================
name = $name;
$this->addonDir = is_null($addonDir) ? FLUX_ADDON_DIR."/$name" : $addonDir;
$this->configDir = "{$this->addonDir}/config";
$this->moduleDir = "{$this->addonDir}/modules";
$this->themeDir = "{$this->addonDir}/themes";
$files = array(
'addonConfig' => "{$this->configDir}/addon.php",
'accessConfig' => "{$this->configDir}/access.php",
//'messagesConfig' => "{$this->configDir}/messages.php" // Deprecated.
);
foreach ($files as $configName => $filename) {
if (file_exists($filename)) {
$this->{$configName} = Flux::parseConfigFile($filename);
}
if (!($this->{$configName} instanceOf Flux_Config)) {
$tempArr = array();
$this->{$configName} = new Flux_Config($tempArr);
}
}
// Use new language system for messages (also supports addons).
$this->messagesConfig = Flux::parseLanguageConfigFile($name);
}
public function respondsTo($module, $action = null)
{
$path = is_null($action) ? "{$this->moduleDir}/$module" : "{$this->moduleDir}/$module/$action.php";
if ((is_null($action) && is_dir($path)) || file_exists($path)) {
return true;
}
else {
return false;
}
}
public function getView(Flux_Template $template, $module, $action)
{
$path = "{$this->themeDir}/". $template->getName() . "/{$module}/{$action}.php";
if (file_exists($path)) {
return $path;
}
if (!empty($template->parentTemplate)) {
return $this->getView( $template->parentTemplate, $module, $action);
}
return false;
}
}
?>
================================================
FILE: lib/Flux/Athena.php
================================================
loginServer = $loginServer;
$this->charServer = $charServer;
$this->mapServer = $mapServer;
$this->loginDatabase = $loginServer->config->getDatabase();
$this->serverName = $charMapConfig->getServerName();
$this->expRates = $charMapConfig->getExpRates()->toArray();
$this->dropRates = $charMapConfig->getDropRates()->toArray();
$this->isRenewal = (boolean)$charMapConfig->getRenewal();
$this->maxCharSlots = (int)$charMapConfig->getMaxCharSlots();
$this->dateTimezone = $charMapConfig->getDateTimezone();
$this->charMapDatabase = $charMapConfig->getDatabase();
$resetDenyMaps = $charMapConfig->getResetDenyMaps();
if (!$resetDenyMaps) {
$this->resetDenyMaps = array('sec_pri');
}
elseif (!is_array($resetDenyMaps)) {
$this->resetDenyMaps = array($resetDenyMaps);
}
else {
$this->resetDenyMaps = $resetDenyMaps->toArray();
}
// Get WoE times specific in servers config.
$woeDayTimes = $charMapConfig->getWoeDayTimes();
if ($woeDayTimes instanceOf Flux_Config) {
$woeDayTimes = $woeDayTimes->toArray();
foreach ($woeDayTimes as $dayTime) {
if (!is_array($dayTime) || count($dayTime) < 4) {
continue;
}
list ($sDay, $sTime, $eDay, $eTime) = array_slice($dayTime, 0, 4);
$sTime = trim($sTime);
$eTime = trim($eTime);
if ($sDay < 0 || $sDay > 6 || $eDay < 0 || $eDay > 6 ||
!preg_match('/^\d{2}:\d{2}$/', $sTime) || !preg_match('/^\d{2}:\d{2}$/', $eTime)) {
continue;
}
$this->woeDayTimes[] = array(
'startingDay' => $sDay,
'startingTime' => $sTime,
'endingDay' => $eDay,
'endingTime' => $eTime
);
}
}
// Config used for disallowing access to certain modules during WoE.
$woeDisallow = $charMapConfig->getWoeDisallow();
$_tempArray = array();
$this->woeDisallow = new Flux_Config($_tempArray);
if ($woeDisallow instanceOf Flux_Config) {
$woeDisallow = $woeDisallow->toArray();
foreach ($woeDisallow as $disallow) {
if (array_key_exists('module', $disallow)) {
$module = $disallow['module'];
if (array_key_exists('action', $disallow)) {
$action = $disallow['action'];
$this->woeDisallow->set("$module.$action", true);
}
else {
$this->woeDisallow->set($module, true);
}
}
}
}
}
/**
* Set connection object.
*
* @param Flux_Connection $connection
* @return Flux_Connection
*/
public function setConnection(Flux_Connection $connection)
{
$this->connection = $connection;
$this->logsDatabase = $connection->logsDbConfig->getDatabase();
$this->webDatabase = $connection->webDbConfig->getDatabase();
return $connection;
}
/**
* Set cart object.
*
* @param Flux_ItemShop_Cart $cart
* @return Flux_ItemShop_Cart
*/
public function setCart(Flux_ItemShop_Cart $cart)
{
$this->cart = $cart;
return $cart;
}
/**
* When casted to a string, the server name should be used.
*
* @return string
* @access public
*/
public function __toString()
{
return $this->serverName;
}
/**
* Transfer credits from one account to another.
*
* @param int $fromAccountID Account ID
* @param string $targetCharName Character name of person receiving credits
* @param int $credits Amount of credits
*/
public function transferCredits($fromAccountID, $targetCharName, $credits)
{
//
// Return values:
// -1 = From or to account, one or the other does not exist. (likely the latter.)
// -2 = Sender has an insufficient balance.
// -3 = Unknown character.
// true = Successful transfer
// false = Error
//
$sql = "SELECT account_id, char_id, name AS char_name FROM {$this->charMapDatabase}.`char` WHERE `char`.name = ? LIMIT 1";
$sth = $this->connection->getStatement($sql);
if (!$sth->execute(array($targetCharName)) || !($char=$sth->fetch())) {
// Unknown character.
return -3;
}
$targetAccountID = $char->account_id;
$targetCharID = $char->char_id;
$sql = "SELECT COUNT(account_id) AS accounts FROM {$this->loginDatabase}.login WHERE ";
$sql .= "account_id = ? OR account_id = ? LIMIT 2";
$sth = $this->connection->getStatement($sql);
if (!$sth->execute(array($fromAccountID, $targetAccountID)) || $sth->fetch()->accounts != 2) {
// One or the other, from or to, are non-existent accounts.
return -1;
}
if (!$this->loginServer->hasCreditsRecord($fromAccountID)) {
// Sender has a zero balance.
return -2;
}
$creditsTable = Flux::config('FluxTables.CreditsTable');
$xferTable = Flux::config('FluxTables.CreditTransferTable');
// Get balance of sender.
$sql = "SELECT balance FROM {$this->loginDatabase}.$creditsTable WHERE account_id = ? LIMIT 1";
$sth = $this->connection->getStatement($sql);
if (!$sth->execute(array($fromAccountID))) {
// Error.
return false;
}
if ($sth->fetch()->balance < $credits) {
// Insufficient balance.
return -2;
}
// Take credits from fromAccount first.
if ($this->loginServer->depositCredits($fromAccountID, -$credits)) {
// Then deposit to targetAccount next.
if (!$this->loginServer->depositCredits($targetAccountID, $credits)) {
// Attempt to restore credits if deposit to toAccount failed.
$this->loginServer->depositCredits($fromAccountID, $credits);
return false;
}
else {
$sql = "INSERT INTO {$this->charMapDatabase}.$xferTable ";
$sql .= "(from_account_id, target_account_id, target_char_id, amount, transfer_date) ";
$sql .= "VALUES (?, ?, ?, ?, NOW())";
$sth = $this->connection->getStatement($sql);
// Log transfer.
$sth->execute(array($fromAccountID, $targetAccountID, $targetCharID, $credits));
return true;
}
}
else {
return false;
}
}
/**
* Set loginAthenaGroup object.
*
* @param Flux_LoginAthenaGroup $loginAthenaGroup
* @return Flux_LoginAthenaGroup
*/
public function setLoginAthenaGroup(Flux_LoginAthenaGroup $loginAthenaGroup)
{
$this->loginAthenaGroup = $loginAthenaGroup;
return $loginAthenaGroup;
}
/**
* Check if a character exists with a particular char ID.
*
* @param int $charID
* @return bool True/false if char exists or doesn't.
*/
public function charExists($charID)
{
$sql = "SELECT char_id FROM {$this->charMapDatabase}.`char` WHERE ";
$sql .= "`char`.char_id = ? LIMIT 1";
$sth = $this->connection->getStatement($sql);
if ($sth->execute(array($charID)) && ($char=$sth->fetch()) && $char->char_id) {
return true;
}
else {
return false;
}
}
/**
* Check if a charID belongs to an accountID.
*
* @param int $charID
* @param int $accountID
* @return bool
*/
public function charBelongsToAccount($charID, $accountID)
{
$sql = "SELECT char_id FROM {$this->charMapDatabase}.`char` WHERE ";
$sql .= "`char`.char_id = ? AND `char`.account_id = ? LIMIT 1";
$sth = $this->connection->getStatement($sql);
if ($sth->execute(array($charID, $accountID)) && ($char=$sth->fetch()) && $char->char_id) {
return true;
}
else {
return false;
}
}
/**
* Check if char with charID is online.
*
* @param int $charID
* @return bool
*/
public function charIsOnline($charID)
{
$sql = "SELECT char_id FROM {$this->charMapDatabase}.`char` WHERE `char`.online > 0 ";
$sql .= "AND `char`.char_id = ? LIMIT 1";
$sth = $this->connection->getStatement($sql);
if ($sth->execute(array($charID)) && ($char=$sth->fetch()) && $char->char_id) {
return true;
}
else {
return false;
}
}
/**
* Check if account has any online characters at the moment.
*
* @param int $accountId
* @return bool
*/
public function accountHasOnlineChars($accountID)
{
$sql = "SELECT char_id FROM {$this->charMapDatabase}.`char` WHERE `char`.online > 0 ";
$sql .= "AND `char`.account_id = ? ORDER BY `char`.online DESC LIMIT 1";
$sth = $this->connection->getStatement($sql);
if ($sth->execute(array($accountID)) && ($char=$sth->fetch()) && $char->char_id) {
return true;
}
else {
return false;
}
}
/**
* Get character data of charID.
*
* @param int $charID
* @return mixed Returns Flux_DataObject or false.
*/
public function getCharacter($charID)
{
$sql = "SELECT `char`.* FROM {$this->charMapDatabase}.`char` WHERE ";
$sql .= "`char`.char_id = ? LIMIT 1";
$sth = $this->connection->getStatement($sql);
if ($sth->execute(array($charID)) && ($char=$sth->fetch())) {
return $char;
}
else {
return false;
}
}
/**
* Get character prefs.
*
* @param int $charID Character ID
* @param array $prefs Only these prefs?
* @return mixed Flux_Config or false.
*/
public function getPrefs($charID, array $prefs = array())
{
$sql = "SELECT account_id FROM {$this->charMapDatabase}.`char` WHERE char_id = ? LIMIT 1";
$sth = $this->connection->getStatement($sql);
if ($sth->execute(array($charID)) && ($char=$sth->fetch())) {
$charPrefsTable = Flux::config('FluxTables.CharacterPrefsTable');
$pref = array();
$bind = array($char->account_id, $charID);
$sql = "SELECT name, value FROM {$this->charMapDatabase}.$charPrefsTable ";
$sql .= "WHERE account_id = ? AND char_id = ?";
if ($prefs) {
foreach ($prefs as $p) {
$pref[] = "name = ?";
$bind[] = $p;
}
$sql .= sprintf(' AND (%s)', implode(' OR ', $pref));
}
$sth = $this->connection->getStatement($sql);
if ($sth->execute($bind)) {
$prefsArray = array();
foreach ($sth->fetchAll() as $p) {
$prefsArray[$p->name] = $p->value;
}
return new Flux_Config($prefsArray);
}
else {
return false;
}
}
else {
return false;
}
}
/**
* Set character prefs.
*
* @param int $charID
* @param array $prefsArray pref=>value pairs.
* @return bool
*/
public function setPrefs($charID, array $prefsArray)
{
$sql = "SELECT account_id FROM {$this->charMapDatabase}.`char` WHERE char_id = ? LIMIT 1";
$sth = $this->connection->getStatement($sql);
if ($sth->execute(array($charID)) && ($char=$sth->fetch())) {
$charPrefsTable = Flux::config('FluxTables.CharacterPrefsTable');
$pref = array();
$bind = array($char->account_id, $charID);
$sql = "SELECT id, name, value FROM {$this->charMapDatabase}.$charPrefsTable ";
$sql .= "WHERE account_id = ? AND char_id = ?";
if ($prefsArray) {
foreach ($prefsArray as $prefName => $prefValue) {
$pref[] = "name = ?";
$bind[] = $prefName;
}
$sql .= sprintf(' AND (%s)', implode(' OR ', $pref));
}
$sth = $this->connection->getStatement($sql);
if ($sth->execute($bind)) {
$prefs = $sth->fetchAll();
$update = array();
$usql = "UPDATE {$this->charMapDatabase}.$charPrefsTable ";
$usql .= "SET value = ? WHERE id = ?";
$usth = $this->connection->getStatement($usql);
$isql = "INSERT INTO {$this->charMapDatabase}.$charPrefsTable ";
$isql .= "(account_id, char_id, name, value, create_date) ";
$isql .= "VALUES (?, ?, ?, ?, NOW())";
$isth = $this->connection->getStatement($isql);
foreach ($prefs as $p) {
$update[$p->name] = $p->id;
}
foreach ($prefsArray as $pref => $value) {
if (array_key_exists($pref, $update)) {
$id = $update[$pref];
$usth->execute(array($value, $id));
}
else {
$isth->execute(array($char->account_id, $charID, $pref, $value));
}
}
return true;
}
else {
return false;
}
}
else {
return false;
}
}
/**
* Get a single character pref.
*
* @param int $charID
* @param string $pref
* @return mixed string or false.
*/
public function getPref($charID, $pref)
{
$prefs = $this->getPrefs($charID, array($pref));
if ($prefs instanceOf Flux_Config) {
return $prefs->get($pref);
}
else {
return false;
}
}
/**
* Set a single character pref.
*
* @param int $charID
* @param string $pref
* @param string $value
* @return bool
*/
public function setPref($charID, $pref, $value)
{
return $this->setPrefs($charID, array($pref => $value));
}
/**
* Re-set the appearance of a character.
*
* @param int $charID
* @return mixed
*/
public function resetLook($charID)
{
// Return values:
// -1 = Character is online, cannot reset.
// -2 = Unknown character.
// false = Failed to reset.
// true = Successfully reset.
$char = $this->getCharacter($charID);
if (!$char) {
return -2;
}
if ($char->online) {
return -1;
}
$sql = "UPDATE {$this->charMapDatabase}.inventory SET ";
$sql .= "equip = 0 WHERE char_id = ?";
$sth = $this->connection->getStatement($sql);
if (!$sth->execute(array($charID))) {
return false;
}
$sql = "UPDATE {$this->charMapDatabase}.`char` SET ";
$sql .= "hair = 1, hair_color = 0, clothes_color = 0, weapon = 0, shield = 0, ";
$sql .= "head_top = 0, head_mid = 0, head_bottom = 0, body = `class` ";
$sql .= "WHERE char_id = ?";
$sth = $this->connection->getStatement($sql);
if (!$sth->execute(array($charID))) {
return false;
}
else {
return true;
}
}
/**
* Re-set the position of a character.
*
* @param int $charID
* @return mixed
*/
public function resetPosition($charID)
{
// Return values:
// -1 = Character is online, cannot reset.
// -2 = Reset cannot be done from current map.
// -3 = Unknown character.
// false = Failed to reset.
// true = Successfully reset.
$char = $this->getCharacter($charID);
if (!$char) {
return -3;
}
if ($char->online) {
return -1;
}
$charMap = basename($char->last_map, '.gat');
foreach ($this->resetDenyMaps as $map) {
$denyMap = basename($map, '.gat');
if ($charMap == $denyMap) {
return -2;
}
}
$sql = "UPDATE {$this->charMapDatabase}.`char` AS ch SET ";
$sql .= "ch.last_map = ch.save_map, ch.last_x = ch.save_x, ch.last_y = ch.save_y ";
$sql .= "WHERE ch.char_id = ?";
$sth = $this->connection->getStatement($sql);
if ($sth->execute(array($charID))) {
return true;
}
else {
return false;
}
}
/**
* Get the current server time, based on the DateTimezone servers.php config.
*
* @param string $format Similar to that of PHP's date() function.
* @return string
*/
public function getServerTime($format = 'U')
{
$dateTime = date_create('now');
if ($this->dateTimezone) {
$dateTime->setTimeZone(new DateTimeZone($this->dateTimezone));
}
return $dateTime->format($format);
}
/**
* Check if it currently WoE according to the configured hours and timezone.
*
* @return bool
*/
public function isWoe()
{
$serverTime = (int)$this->getServerTime();
$dayNames = array('Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday');
foreach ($this->woeDayTimes as $woeDayTime) {
$sDay = $dayNames[$woeDayTime['startingDay']];
$eDay = $dayNames[$woeDayTime['endingDay']];
$start = strtotime("$sDay {$woeDayTime['startingTime']}");
$end = strtotime("$eDay {$woeDayTime['endingTime']}");
if ($serverTime > $start && $serverTime < $end) {
return true;
}
}
return false;
}
}
?>
================================================
FILE: lib/Flux/Authorization.php
================================================
config = $accessConfig;
$this->session = $sessionData;
}
/**
* Get authorization instance, creates one if it doesn't already exist.
*
* @param Flux_Config $accessConfig
* @param Flux_SessionData $sessionData
* @return Flux_Authorization
* @access public
*/
public static function getInstance($accessConfig = null, $sessionData = null)
{
if (!self::$auth) {
self::$auth = new Flux_Authorization($accessConfig, $sessionData);
}
return self::$auth;
}
/**
* Checks whether or not the current user is able to perform a particular
* action based on his/her group level and id.
*
* @param string $moduleName
* @param string $actionName
* @return bool
* @access public
*/
public function actionAllowed($moduleName, $actionName = 'index')
{
$accessConfig = $this->config->get('modules');
$accessKeys = array("$moduleName.$actionName", "$moduleName.*");
$accountLevel = $this->session->account->group_level;
$existentKeys = array();
if ($accessConfig instanceOf Flux_Config) {
foreach ($accessKeys as $accessKey) {
$accessLevel = $accessConfig->get($accessKey);
if (!is_null($accessLevel)) {
$existentKeys[] = $accessKey;
if ($accessLevel == AccountLevel::ANYONE || $accessLevel == $accountLevel ||
($accessLevel != AccountLevel::UNAUTH && $accessLevel <= $accountLevel)) {
return true;
}
}
}
}
if (empty($existentKeys)) {
return -1;
}
else {
return false;
}
}
/**
* Checks whether or not the current user is allowed to use a particular
* feature based on his/her group level and id.
*
* @param string $featureName
* @return bool
* @access public
*/
public function featureAllowed($featureName)
{
$accessConfig = $this->config->get('features');
$accountLevel = $this->session->account->group_level;
if (($accessConfig instanceOf Flux_Config)) {
$accessLevel = $accessConfig->get($featureName);
if (!is_null($accessLevel) &&
($accessLevel == AccountLevel::ANYONE || $accessLevel == $accountLevel ||
($accessLevel != AccountLevel::UNAUTH && $accessLevel <= $accountLevel))) {
return true;
}
}
return false;
}
/**
* Provides convenient getters such as `allowedTo' and
* `getGroupLevelTo'.
*
* @access public
*/
public function __get($prop)
{
if (preg_match("/^allowedTo(.+)/i", $prop, $m)) {
return $this->featureAllowed($m[1]);
}
elseif (preg_match("/^getGroupLevelTo(.+)/i", $prop, $m)) {
$accessConfig = $this->config->get('features');
if ($accessConfig instanceOf Flux_Config) {
return $accessConfig->get($m[1]);
}
}
}
/**
* Wrapper method for setting and getting values from the access config.
*
* @param string $key
* @param mixed $value
* @param arary $options
* @access public
*/
public function config($key, $value = null, $options = array())
{
if (!is_null($value)) {
return $this->config->set($key, $value, $options);
}
else {
return $this->config->get($key);
}
}
}
?>
================================================
FILE: lib/Flux/BaseServer.php
================================================
config = $config;
}
/**
* Checks whether the server is up and running (accepting connections).
* Will return true/false based on the server status.
*
* @return bool Returns true if server is running, false if not.
* @access public
*/
public function isUp()
{
$addr = $this->config->getAddress();
$port = $this->config->getPort();
$sock = @fsockopen($addr, $port, $errno, $errstr, (int)Flux::config('ServerStatusTimeout'));
if (is_resource($sock)) {
fclose($sock);
return true;
}
else {
return false;
}
}
}
?>
================================================
FILE: lib/Flux/Captcha.php
================================================
options = array_merge(
array(
'chars' => 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWWXYZ0123456789',
'length' => 5,
'background' => FLUX_DATA_DIR.'/captcha/background.png',
'fontPath' => realpath(FLUX_DATA_DIR.'/captcha/fonts'),
'fontName' => 'default.ttf',
'fontSize' => 28,
'yPosition' => 40,
'useDistort' => true,
'distortion' => 10
),
$options
);
// Let GD know where our fonts are.
//putenv("GDFONTPATH={$this->options['fontPath']}"); // Possibly breaks on Windows?
// Generate security code.
$this->generateCode();
// Generate CAPTCHA image.
$this->generateImage();
}
/**
* Generate the security code to be used.
*
* @access protected
*/
protected function generateCode()
{
$code = '';
$chars = str_split($this->options['chars']);
for ($i = 0; $i < $this->options['length']; ++$i) {
$code .= $chars[array_rand($chars)];
}
$this->code = $code;
return $code;
}
/**
* Generate the image.
*
* @access protected
*/
protected function generateImage()
{
$this->gd = imagecreatefrompng($this->options['background']);
$yPos = $this->options['yPosition'];
$font = "{$this->options['fontPath']}/{$this->options['fontName']}";
$size = $this->options['fontSize'];
$shade1 = imagecolorallocate($this->gd, 240, 240, 240);
$shade2 = imagecolorallocate($this->gd, 60, 60, 60);
$shade3 = imagecolorallocate($this->gd, 0, 0, 0);
if (function_exists('imagettftext')) {
$distA = -$this->options['distortion'];
$distB = +$this->options['distortion'];
foreach (str_split($this->code, 1) as $i => $char) {
imagettftext($this->gd, $size + 2, $this->options['useDistort'] ? rand($distA, $distB) : 0, ((28 * $i) + 10), $yPos, $shade3, $font, $char);
imagettftext($this->gd, $size + 4, $this->options['useDistort'] ? rand($distA, $distB) : 0, ((28 * $i) + 10), $yPos, $shade2, $font, $char);
imagettftext($this->gd, $size , $this->options['useDistort'] ? rand($distA, $distB) : 0, ((28 * $i) + 10), $yPos, $shade1, $font, $char);
}
}
else {
$text = "FreeType2 is needed\n";
$text .= "for CAPTCHA support.\n";
foreach (explode("\n", $text) as $i => $line) {
imagestring($this->gd, 3, 5, (12 * ($i + 1)), $line, $shade1);
}
}
}
/**
* Display image.
*
* @access public
*/
public function display()
{
header('Content-Type: image/png');
imagepng($this->gd);
exit;
}
public function __destruct()
{
imagedestroy($this->gd);
}
}
?>
================================================
FILE: lib/Flux/CharServer.php
================================================
================================================
FILE: lib/Flux/Config.php
================================================
true, 'force' => true);
/**
* This is here for any developer's convenience, just in case he/she would
* like to re-use this library without having to depend on the Flux_Error
* class to do so. This will cause Flux_Config to raise the exception class
* of your choice.
*
* It's preferable that the developer change the value directly from the
* class, instead of finding ways to do it from code.
*
* @access private
* @var string
*/
private $exceptionClass = 'Flux_Error';
/**
* The list of configuration keys that are cleared if found in the import config.
* These are used for "normal" arrays, not associative arrays. This way is faster than
* checking if each array in the config is associative or not.
* @access private
* @var array
*/
private $clearKeysOnImport = array(
'ThemeName',
'PayPalReceiverEmails',
'PayPalAllowedHosts',
'BanPaymentStatuses',
'ShopImageExtensions',
);
/**
* Construct a Flux_Config instance which acts as a more convenient
* accessor to the specified configuration array.
*
* @param array $configArray Configuration array.
* @access public
*/
public function __construct(array &$configArr)
{
$this->configArr = &$configArr;
}
/**
* This is here... for no real GOOD reason, but should the need arise, at
* least you aren't deprived of access to it.
*
* @return array Configuration array.
* @access public
*/
public function &toArray()
{
return $this->configArr;
}
/**
* Goes through each child in the array which is also an array, and returns
* them collectively as an array of Flux_Config instances.
*
* @return array Array of Flux_Config instances of all children arrays.
* @access public
*/
public function &getChildrenConfigs()
{
$children = array();
foreach ($this->configArr as $key => &$child) {
if (is_array($child)) {
$children[$key] = new Flux_Config($child);
}
}
return $children;
}
/**
* Get the value held by the specified key. If the value is an array it
* will be returned as an instance of Flux_Config by default, unless
* $configObjectIfArray is set to false.
*
* Keys are specified in an object-like format, such as: 'Foo.Bar.Baz'
* where each dot would denote the difference in depth from key-to-key.
*
* @param string $key Key sequence.
* @param bool $configObjectIfArray True/false whether or not to return Flux_Config instances for values that are an array.
* @access public
*/
public function get($key, $configObjectIfArray = true)
{
if (!is_string($key) || empty($key)) {
return null;
}
$keys = explode('.', $key);
$base = &$this->configArr;
$size = count($keys) - 1;
for ($i = 0; $i < $size; ++$i) {
$currentKey = $keys[$i];
if (is_array($base) && array_key_exists($currentKey, $base)) {
$base = &$base[$currentKey];
}
else {
// Short-circuit and return null.
return null;
}
}
$currentKey = $keys[$size];
if (array_key_exists($currentKey, $base)) {
$value = &$base[$currentKey];
if (is_array($value) && $configObjectIfArray) {
$configClassName = get_class($this);
return new $configClassName($value);
}
elseif ($value instanceOf Flux_Config && !$configObjectIfArray) {
return $value->toArray();
}
else {
return $value;
}
}
else {
// We want to avoid a traditional PHP error when referencing
// non-existent keys, so we'll silently return null as an
// alternative ;)
return null;
}
}
/**
* Set a key to hold the specified value. The format for specifying a key
* is 100% identical to Flux_Config::get().
*
* Options outline:
* overwrite - true/false to overwrite existing keys.
* force - true/false to force the creation of the key hierarchy.
*
* @param string $key Key sequence.
* @param mixed $value Value to set in the key.
* @param array $options Array of options.
* @access public
*/
public function set($key, $value, $options = array())
{
$opts = array_merge($this->defaultSetOptions, $options);
$keys = explode('.', $key);
$base = &$this->configArr;
$size = count($keys) - 1;
for ($i = 0; $i < $size; ++$i) {
$currentKey = $keys[$i];
if (is_array($base) && array_key_exists($currentKey, $base)) {
$base = &$base[$currentKey];
}
elseif ($opts['force']) {
$base[$currentKey] = array();
$base = &$base[$currentKey];
}
else {
// Short-circuit and return false.
return false;
}
}
$currentKey = $keys[$size];
if (array_key_exists($currentKey, $base) && !$opts['overwrite']) {
return false;
}
$base[$currentKey] = $value;
return $value;
}
/**
* Convenience method for raising an internal exception.
*
* @param string $message Error message.
* @access public
*/
public function raise($message)
{
$exceptionClass = $this->exceptionClass;
throw new $exceptionClass($message);
}
/**
* Adds the ability to call set() as native methods.
*
* @param string $method
* @param array $args
* @access public
*/
public function __call($method, $args = array())
{
if (preg_match('/^get(\S+)$/', $method, $m)) {
return $this->get($m[1]);
}
elseif (preg_match('/^set(\S+)$/', $method, $m)) {
$options = array();
$argc = count($args);
if ($argc > 1) {
$options = $args[1];
}
elseif ($argc < 1) {
$class = get_class($this);
$this->raise("Missing value argument in $class::$method()");
}
return $this->set($m[1], $args[0], $options);
}
}
/**
*
*/
public function merge(Flux_Config $config, $recursive = true, $distinct = false)
{
if (!$recursive && $distinct) {
$this->raise('Cannot merge non-recursively and distinctively at the same time.');
}
if ($distinct) {
$this->configArr = $this->array_merge_recursive_distinct($this->configArr, $config->toArray());
} else if ($recursive) {
$this->configArr = array_merge_recursive($this->configArr, $config->toArray());
} else {
$this->configArr = array_merge($this->configArr, $config->toArray());
}
}
/**
* array_merge_recursive does indeed merge arrays, but it converts values with duplicate
* keys to arrays rather than overwriting the value in the first array with the duplicate
* value in the second array, as array_merge does. I.e., with array_merge_recursive,
* this happens (documented behavior):
*
* array_merge_recursive(array('key' => 'org value'), array('key' => 'new value'));
* => array('key' => array('org value', 'new value'));
*
* array_merge_recursive_distinct does not change the datatypes of the values in the arrays.
* Matching keys' values in the second array overwrite those in the first array, as is the
* case with array_merge, i.e.:
*
* array_merge_recursive_distinct(array('key' => 'org value'), array('key' => 'new value'));
* => array('key' => array('new value'));
*
* Because arrays are appended, not overwritten, we check each key against $clearKeysOnImport,
* and if it matches, we clear the array before importing the new values.
*
* @param array $array1
* @param array $array2
* @return array
* @author Daniel
* @author Gabriel Sobrinho
*/
public function array_merge_recursive_distinct(array $base, array $import) {
$merged = $base;
foreach ($import as $key => &$value) {
if (in_array($key, $this->clearKeysOnImport)) {
$merged[$key] = array();
}
if (is_array($value) && isset($merged[$key]) && is_array($merged[$key])) {
$merged[$key] = $this->array_merge_recursive_distinct($merged[$key], $value);
} else {
$merged[$key] = $value;
}
}
return $merged;
}
}
?>
================================================
FILE: lib/Flux/Connection/Statement.php
================================================
stmt = $stmt;
if (!self::$errorLog) {
self::$errorLog = new Flux_LogFile(FLUX_DATA_DIR.'/logs/mysql/errors/'.date('Ymd').'.log', 'a');
}
}
private function getParamType($param)
{
switch (true) {
case is_bool($param):
return PDO::PARAM_BOOL;
case is_int($param):
return PDO::PARAM_INT;
case is_null($param):
return PDO::PARAM_NULL;
}
return PDO::PARAM_STR;
}
public function execute(array $inputParameters = array(), $bind_param = false)
{
if ($bind_param) {
foreach ($inputParameters as $key => &$param) {
if (is_string($key) && $key[0] == ":") {
if (is_array($param)) {
// $params = [ :param => [ val, PDO::PARAM_ ], ... ];
$this->stmt->bindParam($key, $param[0], $param[1]);
} else {
// $params = [ :param => val, ... ];
$this->stmt->bindParam($key, $param, $this->getParamType($param));
}
} else {
// $params = [ val, ... ];
$this->stmt->bindParam($key+1, $param, $this->getParamType($param));
}
}
$res = $this->stmt->execute();
} else {
$res = $this->stmt->execute(empty($inputParameters) ? null : $inputParameters);
}
Flux::$numberOfQueries++;
if ((int)$this->stmt->errorCode()) {
$info = $this->stmt->errorInfo();
self::$errorLog->puts('[SQLSTATE=%s] Err %s: %s', $info[0], $info[1], $info[2]);
if (Flux::config('DebugMode')) {
$message = sprintf('MySQL error (SQLSTATE: %s, ERROR: %s): %s', $info[0], $info[1], $info[2]);
throw new Flux_Error($message);
}
}
return $res;
}
public function __call($method, $args)
{
return call_user_func_array(array($this->stmt, $method), $args);
}
}
================================================
FILE: lib/Flux/Connection/index.html
================================================
================================================
FILE: lib/Flux/Connection.php
================================================
dbConfig = $dbConfig;
$this->logsDbConfig = $logsDbConfig;
$this->webDbConfig = $webDbConfig;
}
/**
* Establish connection to server based on config.
*
* @param Flux_Config $dbConfig
* @return PDO
* @access private
*/
private function connect(Flux_Config $dbConfig)
{
$dsn = 'mysql:';
// Differentiate between a socket-type connection or an ip:port
// connection.
if ($sock=$dbConfig->getSocket()) {
$dsn .= "unix_socket=$sock";
}
else {
$dsn .= 'host='.$dbConfig->getHostname();
if ($port=$dbConfig->getPort()) {
$dsn .= ";port=$port";
}
}
// May or may not have a database name specified.
if ($dbName=$dbConfig->getDatabase()) {
$dsn .= ";dbname=$dbName";
}
$persistent = array(PDO::ATTR_PERSISTENT => (bool)$dbConfig->getPersistent());
return new PDO($dsn, $dbConfig->getUsername(), $dbConfig->getPassword(), $persistent);
}
/**
* Get the PDO instance for the main database server connection.
*
* @return PDO
* @access private
*/
private function getConnection()
{
if (!$this->pdoMain) {
// Establish connection for main databases.
$pdoMain = $this->connect($this->dbConfig);
$this->pdoMain = $pdoMain;
if ($encoding=$this->dbConfig->getEncoding()) {
$sth = $this->getStatement("SET NAMES ?");
$sth->execute(array($encoding));
}
if ($timezone=$this->dbConfig->getTimezone()) {
$sth = $this->getStatement("SET time_zone = ?");
$sth->execute(array($timezone));
}
}
return $this->pdoMain;
}
/**
* Get the PDO instance for the logs database server connection.
*
* @return PDO
* @access private
*/
private function getLogsConnection()
{
if (!$this->pdoLogs) {
// Establish separate connection just for the log database.
$pdoLogs = $this->connect($this->logsDbConfig);
$this->pdoLogs = $pdoLogs;
if ($encoding=$this->logsDbConfig->getEncoding()) {
$sth = $this->getStatementForLogs("SET NAMES ?");
$sth->execute(array($encoding));
}
if ($timezone=$this->logsDbConfig->getTimezone()) {
$sth = $this->getStatementForLogs("SET time_zone = ?");
$sth->execute(array($timezone));
}
}
return $this->pdoLogs;
}
/**
* Get the PDO instance for the web server database server connection.
*
* @return PDO
* @access private
*/
private function getWebConnection()
{
if (!$this->pdoWeb) {
// Establish separate connection just for the web server database.
$pdoWeb = $this->connect($this->webDbConfig);
$this->pdoWeb = $pdoWeb;
if ($encoding=$this->webDbConfig->getEncoding()) {
$sth = $this->getStatementForWeb("SET NAMES ?");
$sth->execute(array($encoding));
}
if ($timezone=$this->webDbConfig->getTimezone()) {
$sth = $this->getStatementForWeb("SET time_zone = ?");
$sth->execute(array($timezone));
}
}
return $this->pdoWeb;
}
/**
* Select database to use.
*
* @param string $dbName
* @return mixed
* @access public
*/
public function useDatabase($dbName)
{
if ($this->pdoMain) {
return $this->getStatement("USE $dbName")->execute();
}
else {
return false;
}
}
/**
* Instanciate a PDOStatement without obtaining a PDO handler before-hand.
*
* @return PDOStatement
* @access public
*/
public function getStatement($statement, $options = array())
{
$dbh = $this->getConnection();
$sth = $dbh->prepare($statement, $options);
@$sth->setFetchMode(PDO::FETCH_CLASS, 'Flux_DataObject', array(null, array('dbconfig' => $this->dbConfig)));
if ($sth) {
return new Flux_Connection_Statement($sth);
}
else {
return false;
}
}
/**
* Instanciate a PDOStatement without obtaining a PDO handler before-hand.
*
* @return PDOStatement
* @access public
*/
public function getStatementForLogs($statement, $options = array())
{
$dbh = $this->getLogsConnection();
$sth = $dbh->prepare($statement, $options);
@$sth->setFetchMode(PDO::FETCH_CLASS, 'Flux_DataObject', array(null, array('dbconfig' => $this->logsDbConfig)));
if ($sth) {
return new Flux_Connection_Statement($sth);
}
else {
return false;
}
}
/**
* Instanciate a PDOStatement without obtaining a PDO handler before-hand.
*
* @return PDOStatement
* @access public
*/
public function getStatementForWeb($statement, $options = array())
{
$dbh = $this->getWebConnection();
$sth = $dbh->prepare($statement, $options);
@$sth->setFetchMode(PDO::FETCH_CLASS, 'Flux_DataObject', array(null, array('dbconfig' => $this->webDbConfig)));
if ($sth) {
return new Flux_Connection_Statement($sth);
}
else {
return false;
}
}
/**
*
*/
public function reconnectAs($username, $password)
{
if ($this->pdoMain) {
$this->pdoMain = null;
}
$this->dbConfig->setPersistent(false);
$this->dbConfig->setUsername($username);
$this->dbConfig->setPassword($password);
return true;
}
/**
*
*/
public function isCaseSensitive($database, $table, $column, $useLogsConnection = false)
{
$stm = $useLogsConnection ? 'getStatementForLogs' : 'getStatement';
$sql = 'SELECT COLLATION_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ? AND COLUMN_NAME = ?';
$sth = $this->$stm($sql);
$sth->execute(array($database, $table, $column));
$row = $sth->fetch();
if (preg_match('/_ci$/', $row->COLLATION_NAME)) {
return false;
}
else {
return true;
}
}
}
?>
================================================
FILE: lib/Flux/DataObject.php
================================================
_dbConfig = $defaults['dbconfig'];
unset($defaults['dbconfig']);
}
else {
$tmpArr = array();
$this->_dbConfig = new Flux_Config($tmpArr);
}
$this->_encFrom = $this->_dbConfig->getEncoding();
$this->_encTo = $this->_encFrom ? $this->_dbConfig->get('Convert') : false;
if (!is_null($data)) {
$this->_data = $data;
}
foreach ($defaults as $prop => $value) {
if (!isset($this->_data[$prop])) {
$this->_data[$prop] = $value;
}
}
if ($this->_encTo) {
foreach ($this->_data as $prop => $value) {
$this->_data[$prop] = iconv($this->_encFrom, $this->_encTo, $value);
}
}
}
public function __set($prop, $value)
{
$this->_data[$prop] = $value;
return $value;
}
public function __get($prop)
{
if (isset($this->_data[$prop])) {
return $this->_data[$prop];
}
else {
return null;
}
}
}
?>
================================================
FILE: lib/Flux/Dispatcher.php
================================================
get('basePath');
$paramsArr = $config->get('params');
$modulePath = $config->get('modulePath');
$themePath = $config->get('themePath');
$defaultModule = $config->get('defaultModule');
$themeName = $config->get('themeName');
$defaultAction = $config->get('defaultAction');
$missingActionModuleAction = $config->get('missingActionModuleAction');
$missingViewModuleAction = $config->get('missingViewModuleAction');
$useCleanUrls = $config->get('useCleanUrls');
if (!$defaultModule && $this->defaultModule) {
$defaultModule = $this->defaultModule;
}
if (!$defaultAction && $this->defaultAction) {
$defaultAction = $this->defaultAction;
}
if (!$defaultModule) {
throw new Flux_Error('Please set the default module with $dispatcher->setDefaultModule()');
}
elseif (!$defaultAction) {
throw new Flux_Error('Please set the default action with $dispatcher->setDefaultAction()');
}
if (!$paramsArr) {
$paramsArr = &$_REQUEST;
}
// Provide easier access to parameters.
$params = new Flux_Config($paramsArr);
$baseURI = Flux::config('BaseURI');
if ($params->get('module')) {
$safetyArr = array('..', '/', '\\');
$moduleName = str_replace($safetyArr, '', $params->get('module'));
if ($params->get('action')) {
$actionName = str_replace($safetyArr, '', $params->get('action'));
}
else {
$actionName = $defaultAction;
}
}
elseif (Flux::config('UseCleanUrls')) {
$baseURI = preg_replace('&/+&', '/', rtrim($baseURI, '/')).'/';
$requestURI = preg_replace('&/+&', '/', rtrim($_SERVER['REQUEST_URI'], '/')).'/';
$requestURI = preg_replace('&\?.*?$&', '', $requestURI);
$components = explode('/', trim((string)substr($requestURI, strlen($baseURI)), '/'));
$moduleName = empty($components[0]) ? $defaultModule : $components[0];
$actionName = empty($components[1]) ? $defaultAction : $components[1];
}
elseif (!$params->get('module') && !$params->get('action')) {
$moduleName = $defaultModule;
$actionName = $defaultAction;
}
// Authorization handling.
$auth = Flux_Authorization::getInstance();
if ($auth->actionAllowed($moduleName, $actionName) === false) {
if (!Flux::$sessionData->isLoggedIn()) {
Flux::$sessionData->setMessageData('Please log-in to continue.');
$this->loginRequired($baseURI);
}
else {
$moduleName = 'unauthorized';
$actionName = $this->defaultAction;
}
}
$params->set('module', $moduleName);
$params->set('action', $actionName);
$templateArray = array(
'params' => $params,
'basePath' => $basePath,
'modulePath' => $modulePath,
'moduleName' => $moduleName,
'themePath' => $themePath,
'themeName' => $themeName,
'actionName' => $actionName,
'viewName' => $actionName,
'headerName' => 'header',
'footerName' => 'footer',
'missingActionModuleAction' => $missingActionModuleAction,
'missingViewModuleAction' => $missingViewModuleAction,
'useCleanUrls' => $useCleanUrls
);
$templateConfig = new Flux_Config($templateArray);
$template = new Flux_Template($templateConfig);
// Default data available to all actions and views.
$data = array(
'auth' => Flux_Authorization::getInstance(),
'session' => Flux::$sessionData,
'params' => $params
);
$template->setDefaultData($data);
// Render template! :D
$template->render();
}
/**
* This usually needs to be called after instanciating the dispatcher, as
* it's very necessary to the dispatcher's failsafe functionality.
*
* @param string $module Module name
* @return string
* @access public
*/
public function setDefaultModule($module)
{
$this->defaultModule = $module;
return $module;
}
/**
* (DEPRECATED) By default, 'index' is the default action for any module, but you may
* override that by using this method.
*
* @param string $action Action name
* @return string
* @access public
*/
public function setDefaultAction($action)
{
$this->defaultAction = $action;
return $action;
}
/**
* Redirect to login page if the user is not currently logged in.
*
* @param string $baseURI
* @param string $loginModule
* @param string $loginAction
* @access private
*/
public function loginRequired($baseURI, $message = null, $loginModule = 'account', $loginAction = 'login')
{
$session = Flux::$sessionData;
if (!$message) {
$message = 'Please login to continue.';
}
if (!$session->isLoggedIn()) {
if (Flux::config('UseCleanUrls')) {
$loginURL = sprintf('%s/%s/%s/?return_url=%s',
$baseURI, $loginModule, $loginAction, rawurlencode($_SERVER['REQUEST_URI']));
}
else {
$loginURL = sprintf('%s/?module=%s&action=%s&return_url=%s',
$baseURI, rawurlencode($loginModule), rawurlencode($loginAction), rawurlencode($_SERVER['REQUEST_URI']));
}
$session->setMessageData($message);
header('Location: '.preg_replace('&/{2,}&', '/', $loginURL));
exit;
}
}
}
?>
================================================
FILE: lib/Flux/EmblemExporter.php
================================================
loginAthenaGroup = $loginAthenaGroup;
}
/**
*
*/
public function addAthenaServer(Flux_Athena $athenaServer)
{
if (!in_array($athenaServer, $this->loginAthenaGroup->athenaServers, true)) {
throw new Flux_Error(
"{$athenaServer->serverName} is not a valid char/map server defined in the {$this->loginAthenaGroup->serverName} group.");
}
$this->athenaServers[$athenaServer->serverName] = $athenaServer;
}
/**
*
*/
public function exportArchive()
{
$topDir = $this->sanitizePathName($this->loginAthenaGroup->serverName);
$tmpDir = FLUX_DATA_DIR.'/tmp';
$tmpFile = tempnam($tmpDir, 'zip');
// Create zip archive.
$zip = new ZipArchive();
$zip->open($tmpFile, ZIPARCHIVE::OVERWRITE);
$zip->addEmptyDir($topDir);
foreach ($this->athenaServers as $athenaServer) {
$athenaDir = $this->sanitizePathName($athenaServer->serverName);
$zip->addEmptyDir("$topDir/$athenaDir");
$sql = "SELECT name, emblem_data FROM {$athenaServer->charMapDatabase}.guild WHERE emblem_len > 0 ORDER BY name ASC";
$sth = $athenaServer->connection->getStatement($sql);
$sth->execute();
$guilds = $sth->fetchAll();
if ($guilds) {
foreach ($guilds as $guild) {
$emblemData = @gzuncompress(pack('H*', $guild->emblem_data));
$emblemImage = imagecreatefrombmpstring($emblemData);
ob_start();
imagepng($emblemImage);
$data = ob_get_clean();
$emblemName = sprintf('%s.png', $this->sanitizePathName($guild->name));
$zip->addFromString("$topDir/$athenaDir/$emblemName", $data);
}
}
}
// Close archive.
$zip->close();
// Send out appropriate HTTP headers.
$filename = urlencode(sprintf('%s-%s-emblems.zip', strtolower($topDir), date('Ymd')));
header('Content-Type: application/zip');
header('Content-Length: '.filesize($tmpFile));
header("Content-Disposition: attachment; filename=$filename");
// Read contents of the file.
readfile($tmpFile);
// Remove temporary file.
unlink($tmpFile);
exit;
}
/**
*
*/
private function sanitizePathName($pathName)
{
return preg_replace('/[^\w\d ]+/', '', $pathName);
}
}
?>
================================================
FILE: lib/Flux/Error.php
================================================
================================================
FILE: lib/Flux/FileLoad.php
================================================
'The uploaded file exceeds the upload_max_filesize directive in php.ini',
2 => 'The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form',
3 => 'The uploaded file was only partially uploaded',
4 => 'No file was uploaded',
6 => 'Missing a temporary folder',
7 => 'Failed to write file to disk',
8 => 'A PHP extension stopped the file upload'
);
public function load($file, $path){
$this->path = $path;
if($file->get('error')){
return $this->errorCodeMessages[$file->get('error')];
}
if(is_uploaded_file($file->get('tmp_name'))) {
if(move_uploaded_file($file->get('tmp_name'), $path)) {
return true;
}
}
return 'During the boot file error occurred';
}
public function delete(){
unlink($this->path);
}
}
================================================
FILE: lib/Flux/Installer/CharMapServer.php
================================================
mainServer = $mainServer;
$this->athena = $athena;
$this->schemas = Flux_Installer_Schema::getSchemas($mainServer, $this);
}
}
?>
================================================
FILE: lib/Flux/Installer/MainServer.php
================================================
loginAthenaGroup = $loginAthenaGroup;
$this->schemas = Flux_Installer_Schema::getSchemas($this);
if (array_key_exists($loginAthenaGroup->serverName, Flux::$athenaServerRegistry)) {
foreach (Flux::$athenaServerRegistry[$loginAthenaGroup->serverName] as $athena) {
$this->charMapServers[$athena->serverName] = new Flux_Installer_CharMapServer($this, $athena);
}
}
}
/**
*
*/
public function updateAll()
{
foreach ($this->schemas as $schema) {
if (!$schema->isLatest()) {
$schema->update();
}
}
foreach ($this->charMapServers as $charMapServer) {
foreach ($charMapServer->schemas as $schema) {
if (!$schema->isLatest()) {
$schema->update();
}
}
}
}
}
?>
================================================
FILE: lib/Flux/Installer/Schema.php
================================================
mainServerName = $dataArray['mainServerName'];
$this->charMapServerName = $dataArray['charMapServerName'];
$this->databaseName = $dataArray['databaseName'];
$this->connection = $dataArray['connection'];
$this->availableSchemaDir = $dataArray['availableSchemaDir'];
$this->installedSchemaDir = $dataArray['installedSchemaDir'];
$this->schemaInfo = $dataArray['schemaInfo'];
ksort($this->schemaInfo['versions']);
$this->determineInstalledVersions();
}
/**
*
*/
protected function determineInstalledVersions()
{
foreach ($this->schemaInfo['versions'] as $version => $installed) {
if ($installed) {
$this->versionInstalled = $version;
}
$this->latestVersion = $version;
$this->latestVersionInstalled = $installed;
}
}
/**
*
*/
public function install($version)
{
if (!array_key_exists($version, $this->schemaInfo['versions']) || !$this->schemaInfo['versions'][$version]) {
// Switch database.
$this->connection->useDatabase($this->databaseName);
// Get schema content.
$sql = file_get_contents($this->schemaInfo['files'][$version]);
$sth = $this->connection->getStatement($sql);
// Execute.
$sth->execute();
if ($sth->errorCode()) {
$errorInfo = $sth->errorInfo();
if (count($errorInfo) > 1) {
list ($sqlstate, $errnum, $errmsg) = $errorInfo;
} else {
$errmsg = implode(', ', $errorInfo);
}
if (!empty($errnum) && $errnum == 1045) {
throw new Flux_Error("Critical MySQL error in Installer/Updater: $errnum: $errmsg");
}
elseif (!empty($errnum) && $errnum == 1142) {
// Bail-out.
$message = "MySQL error: $errmsg\n\n";
throw new Flux_Installer_SchemaPermissionError(
$message,
$this->schemaInfo['files'][$version],
$this->databaseName,
$this->mainServerName,
$this->charMapServerName,
$sql
);
}
elseif (!empty($errmsg) && strcmp($errmsg,'00000') != 0) {
// Only quit with an error if there is actually an error.
throw new Flux_Error("Critical MySQL error in Installer/Updater: $errmsg");
}
}
$this->schemaInfo['versions'][$version] = true;
$this->determineInstalledVersions();
// Create file indicating schema is installed.
$file = "{$this->schemaInfo['name']}.$version.txt";
fclose(fopen("{$this->installedSchemaDir}/{$file}", 'w'));
}
else {
return false;
}
}
/**
*
*/
public function update()
{
if (!$this->isLatest()) {
foreach ($this->schemaInfo['versions'] as $version => $installed) {
if (!$installed) {
$this->install($version);
}
}
return true;
}
else {
return false;
}
}
/**
*
*/
public function versionInstalled($version)
{
$installed = array_key_exists($version, $this->schemaInfo['versions']) && $this->schemaInfo['versions'][$version];
return $installed;
}
/**
*
*/
public function isLatest()
{
return $this->latestVersionInstalled;
}
/**
*
*/
public static function getSchemas(Flux_Installer_MainServer $mainServer, Flux_Installer_CharMapServer $charMapServer = null)
{
$mainServerName = $mainServer->loginAthenaGroup->serverName;
if (is_null($charMapServer)) {
$charMapServerName = null;
$databaseName = $mainServer->loginAthenaGroup->loginDatabase;
$connection = $mainServer->loginAthenaGroup->connection;
$availableSchemaDir = array(FLUX_DATA_DIR."/schemas/logindb");
$installedSchemaDir = FLUX_DATA_DIR."/logs/schemas/logindb/$mainServerName";
foreach (Flux::$addons as $addon) {
$_schemaDir = "{$addon->addonDir}/schemas/logindb";
if (file_exists($_schemaDir) && is_dir($_schemaDir)) {
$availableSchemaDir[] = $_schemaDir;
}
}
}
else {
$charMapServerName = $charMapServer->athena->serverName;
$databaseName = $charMapServer->athena->charMapDatabase;
$connection = $charMapServer->athena->connection;
$availableSchemaDir = array(FLUX_DATA_DIR."/schemas/charmapdb");
$installedSchemaDir = FLUX_DATA_DIR."/logs/schemas/charmapdb/$mainServerName/{$charMapServer->athena->serverName}";
foreach (Flux::$addons as $addon) {
$_schemaDir = "{$addon->addonDir}/schemas/charmapdb";
if (file_exists($_schemaDir) && is_dir($_schemaDir)) {
$availableSchemaDir[] = $_schemaDir;
}
}
}
$dataArray = array(
'mainServerName' => $mainServerName,
'charMapServerName' => $charMapServerName,
'databaseName' => $databaseName,
'connection' => $connection,
//'availableSchemaDir' => $availableSchemaDir,
'installedSchemaDir' => $installedSchemaDir,
);
$availableSchemas = array();
$installedSchemas = array();
$directories = array();
foreach ($availableSchemaDir as $dir) {
$directories[] = array($dir, 'availableSchemas', 'sql');
}
$directories[] = array($installedSchemaDir, 'installedSchemas', 'txt');
foreach ($directories as $directory) {
list ($schemaDir, $schemaArray, $fileExt) = $directory;
$schemas = &$$schemaArray;
$sFiles = glob("$schemaDir/*.$fileExt");
if (!$sFiles) {
$sFiles = array();
}
foreach ($sFiles as $schemaFilePath) {
$schemaName = basename($schemaFilePath, ".$fileExt");
if (preg_match('/^(\w+)\.(\d+)$/', $schemaName, $m)) {
$schemaName = $m[1];
$schemaVersion = $m[2];
// Dynamically set schema directory.
$dataArray['availableSchemaDir'] = $directory;
if (!array_key_exists($schemaName, $schemas)) {
$schemas[$schemaName] = array(
'name' => $schemaName,
'versions' => array(),
);
}
$schemas[$schemaName]['versions'][$schemaFilePath] = $schemaVersion;
}
}
}
$objects = array();
foreach ($availableSchemas as $schemaName => $schema) {
$schemaInfo = array(
'name' => $schemaName,
'versions' => array_flip($schema['versions']),
'files' => array_flip($schema['versions'])
);
foreach ($schemaInfo['versions'] as $key => $value) {
$schemaInfo['versions'][$key] = false;
}
if (array_key_exists($schemaName, $installedSchemas)) {
foreach ($installedSchemas[$schemaName]['versions'] as $key => $value) {
$schemaInfo['versions'][$value] = true;
}
}
$dataArray['schemaInfo'] = $schemaInfo;
$objects[] = new Flux_Installer_Schema($dataArray);
}
return $objects;
}
}
?>
================================================
FILE: lib/Flux/Installer/SchemaPermissionError.php
================================================
schemaFile = $schemaFile;
$this->databaseName = $databaseName;
$this->mainServerName = $mainServerName;
$this->charMapServerName = $charMapServerName;
$this->query = $query;
}
public function isLoginDbSchema()
{
return $this->mainServerName && !$this->charMapServerName;
}
public function isCharMapDbSchema()
{
return $this->mainServerName && $this->charMapServerName;
}
}
?>
================================================
FILE: lib/Flux/Installer/index.html
================================================
================================================
FILE: lib/Flux/Installer.php
================================================
$loginAthenaGroup) {
$this->servers[$serverName] = new Flux_Installer_MainServer($loginAthenaGroup);
}
}
/**
*
*/
public static function getInstance()
{
if (!self::$installer) {
self::$installer = new Flux_Installer();
}
return self::$installer;
}
/**
*
*/
public function updateNeeded()
{
foreach ($this->servers as $mainServer) {
foreach ($mainServer->schemas as $schema) {
if (!$schema->isLatest()) {
return true;
}
}
foreach ($mainServer->charMapServers as $charMapServer) {
foreach ($charMapServer->schemas as $schema) {
if (!$schema->isLatest()) {
return true;
}
}
}
}
return false;
}
/**
*
*/
public function updateAll()
{
foreach ($this->servers as $mainServer) {
foreach ($mainServer->schemas as $schema) {
if (!$schema->isLatest()) {
$schema->update();
}
}
foreach ($mainServer->charMapServers as $charMapServer) {
foreach ($charMapServer->schemas as $schema) {
if (!$schema->isLatest()) {
$schema->update();
}
}
}
}
}
}
?>
================================================
FILE: lib/Flux/ItemExistsError.php
================================================
================================================
FILE: lib/Flux/ItemShop/Cart.php
================================================
account = $account;
return $account;
}
public function requiresAccount()
{
if (!$this->account) {
throw new Flux_Error('Account is required to use the shopping cart.');
}
}
public function add(Flux_DataObject $item)
{
$this->cart[] = $item;
return $item;
}
public function delete(Flux_DataObject $item, $deleteAll = false)
{
$deleted = array();
foreach ($this->cart as $cartItem) {
if ($cartItem == $item) {
if ($deleteAll) {
$deleted[] = $cartItem;
}
else {
return $cartItem;
}
}
}
if ($deleted) {
return $deleted;
}
else {
return false;
}
}
public function deleteAll(Flux_DataObject $item)
{
return $this->delete($item, true);
}
public function clear()
{
$itemCount = count($this->cart);
$this->cart = array();
return $itemCount;
}
public function buy(Flux_ItemShop $fromShop)
{
if (!$this->hasFunds()) {
return false;
}
$successful = array();
foreach ($this->cart as $cartItem) {
$successful[] = array(
'item' => $cartItem,
'name' => $cartItem->shop_item_name,
'cost' => $cartItem->shop_item_cost,
'quantity' => $cartItem->shop_item_qty,
'success' => $fromShop->buy($cartItem->shop_item_id)
);
}
$this->clear();
return $successful;
}
public function getCartItems()
{
return $this->cart;
}
public function getCartItemNames()
{
$names = array();
foreach ($this->cart as $cartItem) {
$names[] = $cartItem->shop_item_name;
}
return $names;
}
public function getTotal()
{
$total = 0;
foreach ($this->cart as $cartItem) {
$total += $cartItem->shop_item_cost;
}
return $total;
}
public function hasFunds()
{
$this->requiresAccount();
$creditsAvailable = $this->account->balance;
$creditsNeeded = $this->getTotal();
if ($creditsAvailable < $creditsNeeded) {
return false;
}
else {
return true;
}
}
/**
* Check if the cart is empty.
*/
public function isEmpty()
{
$empty = !count($this->cart);
return $empty;
}
public function deleteByItemNum($num)
{
if (!is_array($num)) {
$num = array((int)$num);
}
else {
$num = array_map('intval', $num);
}
$nDeleted = 0;
foreach ($num as $n) {
if (array_key_exists($n, $this->cart)) {
unset($this->cart[$n]);
$nDeleted += 1;
}
}
return $nDeleted;
}
}
?>
================================================
FILE: lib/Flux/ItemShop/index.html
================================================
================================================
FILE: lib/Flux/ItemShop.php
================================================
server = $server;
}
/**
* Add an item to the shop.
*/
public function add($itemID, $categoryID, $cost, $quantity, $info, $useExisting = 0)
{
$db = $this->server->charMapDatabase;
$table = Flux::config('FluxTables.ItemShopTable');
$sql = "INSERT INTO $db.$table (nameid, category, quantity, cost, info, use_existing, create_date) VALUES (?, ?, ?, ?, ?, ?, NOW())";
$sth = $this->server->connection->getStatement($sql);
$res = $sth->execute(array($itemID, $categoryID, $quantity, $cost, $info, $useExisting));
$sth2 = $this->server->connection->getStatement('SELECT LAST_INSERT_ID() AS insID');
$res2 = $sth2->execute();
if ($res && $res2 && ($insertID=$sth2->fetch()->insID)) {
return $insertID;
}
else {
return false;
}
}
/**
* Modify item info in the shop.
*/
public function edit($shopItemID, $categoryID = null, $cost = null, $quantity = null, $info = null, $useExisting = null)
{
$catQ = '';
$crdQ = '';
$qtyQ = '';
$infQ = '';
$imgQ = '';
$bind = array();
if (!is_null($categoryID)) {
$catQ = "category = ? ";
$bind[] = (int)$categoryID;
}
if (!is_null($cost)) {
if ($catQ) {
$crdQ = ", cost = ? ";
}
else {
$crdQ = "cost = ? ";
}
$bind[] = (int)$cost;
}
if (!is_null($quantity)) {
if ($crdQ) {
$qtyQ = ', quantity = ? ';
}
else {
$qtyQ = "quantity = ? ";
}
$bind[] = (int)$quantity;
}
if (!is_null($info)) {
if ($qtyQ) {
$infQ = ', info = ? ';
}
else {
$infQ = "info = ? ";
}
$bind[] = trim($info);
}
if (!is_null($useExisting)) {
if ($infQ) {
$imgQ = ', use_existing = ? ';
}
else {
$imgQ = "use_existing = ? ";
}
$bind[] = (int)$useExisting;
}
if (empty($bind)) {
return false;
}
$db = $this->server->charMapDatabase;
$table = Flux::config('FluxTables.ItemShopTable');
$sql = "UPDATE $db.$table SET $catQ $crdQ $qtyQ $infQ $imgQ WHERE id = ?";
$sth = $this->server->connection->getStatement($sql);
$bind[] = $shopItemID;
return $sth->execute($bind);
}
/**
*
*/
public function delete($shopItemID)
{
$db = $this->server->charMapDatabase;
$table = Flux::config('FluxTables.ItemShopTable');
$sql = "DELETE FROM $db.$table WHERE id = ?";
$sth = $this->server->connection->getStatement($sql);
return $sth->execute(array($shopItemID));
}
/**
*
*/
public function buy(Flux_DataObject $account, $shopItemID)
{
}
/**
*
*/
public function getItem($shopItemID)
{
$db = $this->server->charMapDatabase;
if($this->server->isRenewal) {
$fromTables = array("$db.item_db_re", "$db.item_db2_re");
} else {
$fromTables = array("$db.item_db", "$db.item_db2");
}
$temp = new Flux_TemporaryTable($this->server->connection, "$db.items", $fromTables);
$shop = Flux::config('FluxTables.ItemShopTable');
$col = "$shop.id AS shop_item_id, $shop.category AS shop_item_category, $shop.cost AS shop_item_cost, $shop.quantity AS shop_item_qty, $shop.use_existing AS shop_item_use_existing, ";
$col .= "$shop.nameid AS shop_item_nameid, $shop.info AS shop_item_info, items.name_english AS shop_item_name";
$sql = "SELECT $col FROM $db.$shop LEFT OUTER JOIN $db.items ON items.id = $shop.nameid WHERE $shop.id = ?";
$sth = $this->server->connection->getStatement($sql);
if ($sth->execute(array($shopItemID))) {
return $sth->fetch();
}
else {
return false;
}
}
/**
*
*/
public function getItems($paginator, $categoryID = null)
{
$sqlpartial = "";
$bind = array();
$db = $this->server->charMapDatabase;
if($this->server->isRenewal) {
$fromTables = array("$db.item_db_re", "$db.item_db2_re");
} else {
$fromTables = array("$db.item_db", "$db.item_db2");
}
$temp = new Flux_TemporaryTable($this->server->connection, "$db.items", $fromTables);
$shop = Flux::config('FluxTables.ItemShopTable');
$col = "$shop.id AS shop_item_id, $shop.cost AS shop_item_cost, $shop.quantity AS shop_item_qty, $shop.use_existing AS shop_item_use_existing, ";
$col .= "$shop.nameid AS shop_item_nameid, $shop.info AS shop_item_info, items.name_english AS shop_item_name";
if (!is_null($categoryID)) {
$sqlpartial = " WHERE $shop.category = ?";
$bind[] = $categoryID;
}
$sql = $paginator->getSQL("SELECT $col FROM $db.$shop LEFT OUTER JOIN $db.items ON items.id = $shop.nameid $sqlpartial");
$sth = $this->server->connection->getStatement($sql);
if ($sth->execute($bind)) {
return $sth->fetchAll();
}
else {
return false;
}
}
/**
*
*/
public function deleteShopItemImage($shopItemID)
{
$serverName = $this->server->loginAthenaGroup->serverName;
$athenaServerName = $this->server->serverName;
$dir = FLUX_DATA_DIR."/itemshop/$serverName/$athenaServerName";
$files = glob("$dir/$shopItemID.*");
foreach ($files as $file) {
unlink($file);
}
return true;
}
/**
*
*/
public function uploadShopItemImage($shopItemID, Flux_Config $file)
{
if ($file->get('error')) {
return false;
}
$validexts = array_map('strtolower', Flux::config('ShopImageExtensions')->toArray());
$extension = strtolower(pathinfo($file->get('name'), PATHINFO_EXTENSION));
if (!in_array($extension, $validexts)) {
return false;
}
$serverName = $this->server->loginAthenaGroup->serverName;
$athenaServerName = $this->server->serverName;
$dir = FLUX_DATA_DIR."/itemshop/$serverName/$athenaServerName";
if (!is_dir(FLUX_DATA_DIR."/itemshop/$serverName")) {
mkdir(FLUX_DATA_DIR."/itemshop/$serverName");
}
if (!is_dir($dir)) {
mkdir($dir);
}
$this->deleteShopItemImage($shopItemID);
if (move_uploaded_file($file->get('tmp_name'), "$dir/$shopItemID.$extension")) {
return true;
}
else {
return false;
}
}
}
?>
================================================
FILE: lib/Flux/LogFile.php
================================================
filename = "$filename.php";
$isNewFile = !file_exists($this->filename);
if ($isNewFile) {
touch($this->filename);
chmod($this->filename, 0600);
}
$this->fp = fopen($this->filename, $mode);
if ($isNewFile) {
fputs($this->fp, "\n");
}
}
/**
* Close file handle.
*/
public function __destruct()
{
if ($this->fp) {
fclose($this->fp);
}
}
/**
* Write a line to the log file.
*
* @param string $format
* @param string $var, ...
* @access public
*/
public function puts()
{
$args = func_get_args();
if (count($args) > 0) {
$args[0] = sprintf("%s%s\n", date($this->dateFormat), $args[0]);
$arguments = array_merge(array($this->fp), $args);
return call_user_func_array('fprintf', $arguments);
}
else {
return false;
}
}
}
?>
================================================
FILE: lib/Flux/LoginAthenaGroup.php
================================================
serverName = $serverName;
$this->connection = $connection;
$this->loginServer = $loginServer;
$this->loginDatabase = $loginServer->config->getDatabase();
$this->logsDatabase = $connection->logsDbConfig->getDatabase();
$this->webDatabase = $connection->webDbConfig->getDatabase();
// Assign connection to LoginServer, used mainly to enable
// authentication feature.
$this->loginServer->setConnection($connection);
foreach ($athenaServers as $athenaServer) {
$this->addAthenaServer($athenaServer);
}
}
/**
* Add an Athena instance to the current collection.
*
* @return mixed Returns false if login servers aren't identical.
* @access public
*/
public function addAthenaServer(Flux_Athena $athenaServer)
{
if ($athenaServer->loginServer === $this->loginServer) {
$athenaServer->setLoginAthenaGroup($this);
$athenaServer->setConnection($this->connection);
$this->athenaServers[] = $athenaServer;
return $this->athenaServers;
}
else {
return false;
}
}
/**
* See Flux_LoginServer->isAuth().
*
* @param string $username
* @param string $password
* @return bool
* @access public
*/
public function isAuth($username, $password)
{
return $this->loginServer->isAuth($username, $password);
}
}
?>
================================================
FILE: lib/Flux/LoginError.php
================================================
================================================
FILE: lib/Flux/LoginServer.php
================================================
loginDatabase = $config->getDatabase();
}
/**
* Set the connection object to be used for this LoginServer instance.
*
* @param Flux_Connection $connection
* @return Flux_Connection
* @access public
*/
public function setConnection(Flux_Connection $connection)
{
$this->connection = $connection;
$this->logsDatabase = $connection->logsDbConfig->getDatabase();
$this->webDatabase = $connection->webDbConfig->getDatabase();
return $connection;
}
/**
* Validate credentials against the login server's database information.
*
* @param string $username Ragnarok account username.
* @param string $password Ragnarok account password.
* @return bool True/false if valid or invalid.
* @access public
*/
public function isAuth($username, $password)
{
if (trim($username) == '' || trim($password) == '') {
return false;
}
if ($this->config->get('UseMD5')) {
$password = Flux::hashPassword($password);
}
$sql = "SELECT userid FROM {$this->loginDatabase}.login WHERE sex != 'S' AND group_id >= 0 ";
if ($this->config->getNoCase()) {
$sql .= 'AND LOWER(userid) = LOWER(?) ';
}
else {
$sql .= 'AND CAST(userid AS BINARY) = ? ';
}
$sql .= "AND user_pass = ? LIMIT 1";
$sth = $this->connection->getStatement($sql);
$sth->execute(array($username, $password));
$res = $sth->fetch();
if ($res) {
return true;
}
else {
return false;
}
}
/**
*
*/
public function register($username, $password, $confirmPassword, $email,$email2, $gender, $birthdate, $securityCode)
{
if (preg_match('/[^' . Flux::config('UsernameAllowedChars') . ']/', $username)) {
throw new Flux_RegisterError('Invalid character(s) used in username', Flux_RegisterError::INVALID_USERNAME);
}
elseif (strlen($username) < Flux::config('MinUsernameLength')) {
throw new Flux_RegisterError('Username is too short', Flux_RegisterError::USERNAME_TOO_SHORT);
}
elseif (strlen($username) > Flux::config('MaxUsernameLength')) {
throw new Flux_RegisterError('Username is too long', Flux_RegisterError::USERNAME_TOO_LONG);
}
elseif (!Flux::config('AllowUserInPassword') && stripos($password, $username) !== false) {
throw new Flux_RegisterError('Password contains username', Flux_RegisterError::PASSWORD_HAS_USERNAME);
}
elseif (!ctype_graph($password)) {
throw new Flux_RegisterError('Invalid character(s) used in password', Flux_RegisterError::INVALID_PASSWORD);
}
elseif (strlen($password) < Flux::config('MinPasswordLength')) {
throw new Flux_RegisterError('Password is too short', Flux_RegisterError::PASSWORD_TOO_SHORT);
}
elseif (strlen($password) > Flux::config('MaxPasswordLength')) {
throw new Flux_RegisterError('Password is too long', Flux_RegisterError::PASSWORD_TOO_LONG);
}
elseif ($password !== $confirmPassword) {
throw new Flux_RegisterError('Passwords do not match', Flux_RegisterError::PASSWORD_MISMATCH);
}
elseif (Flux::config('PasswordMinUpper') > 0 && preg_match_all('/[A-Z]/', $password, $matches) < Flux::config('PasswordMinUpper')) {
throw new Flux_RegisterError('Passwords must contain at least ' . intval(Flux::config('PasswordMinUpper')) . ' uppercase letter(s)', Flux_RegisterError::PASSWORD_NEED_UPPER);
}
elseif (Flux::config('PasswordMinLower') > 0 && preg_match_all('/[a-z]/', $password, $matches) < Flux::config('PasswordMinLower')) {
throw new Flux_RegisterError('Passwords must contain at least ' . intval(Flux::config('PasswordMinLower')) . ' lowercase letter(s)', Flux_RegisterError::PASSWORD_NEED_LOWER);
}
elseif (Flux::config('PasswordMinNumber') > 0 && preg_match_all('/[0-9]/', $password, $matches) < Flux::config('PasswordMinNumber')) {
throw new Flux_RegisterError('Passwords must contain at least ' . intval(Flux::config('PasswordMinNumber')) . ' number(s)', Flux_RegisterError::PASSWORD_NEED_NUMBER);
}
elseif (Flux::config('PasswordMinSymbol') > 0 && preg_match_all('/[^A-Za-z0-9]/', $password, $matches) < Flux::config('PasswordMinSymbol')) {
throw new Flux_RegisterError('Passwords must contain at least ' . intval(Flux::config('PasswordMinSymbol')) . ' symbol(s)', Flux_RegisterError::PASSWORD_NEED_SYMBOL);
}
elseif (!preg_match('/^(.+?)@(.+?)$/', $email)) {
throw new Flux_RegisterError('Invalid e-mail address', Flux_RegisterError::INVALID_EMAIL_ADDRESS);
}
elseif ($email!==$email2) {
throw new Flux_RegisterError('Email do not match', Flux_RegisterError::INVALID_EMAIL_CONF);
}
elseif (!in_array(strtoupper($gender), array('M', 'F'))) {
throw new Flux_RegisterError('Invalid gender', Flux_RegisterError::INVALID_GENDER);
}
elseif (($birthdatestamp = strtotime($birthdate)) === false || date('Y-m-d', $birthdatestamp) != $birthdate) {
throw new Flux_RegisterError('Invalid birthdate', Flux_RegisterError::INVALID_BIRTHDATE);
}
elseif (Flux::config('UseCaptcha')) {
if (Flux::config('EnableReCaptcha')) {
if(isset($_POST['g-recaptcha-response']) && $_POST['g-recaptcha-response'] != ""){
$response = file_get_contents("https://www.google.com/recaptcha/api/siteverify?secret=".Flux::config('ReCaptchaPrivateKey')."&response=".$_POST['g-recaptcha-response']."&remoteip=".$_SERVER['REMOTE_ADDR']);
}
$responseKeys = json_decode($response,true);
if(intval($responseKeys["success"]) !== 1) {
throw new Flux_RegisterError('Invalid security code', Flux_RegisterError::INVALID_SECURITY_CODE);
}
}
elseif (strtolower($securityCode) !== strtolower(Flux::$sessionData->securityCode)) {
throw new Flux_RegisterError('Invalid security code', Flux_RegisterError::INVALID_SECURITY_CODE);
}
}
$sql = "SELECT userid FROM {$this->loginDatabase}.login WHERE ";
if ($this->config->getNoCase()) {
$sql .= 'LOWER(userid) = LOWER(?) ';
}
else {
$sql .= 'BINARY userid = ? ';
}
$sql .= 'LIMIT 1';
$sth = $this->connection->getStatement($sql);
$sth->execute(array($username));
$res = $sth->fetch();
if ($res) {
throw new Flux_RegisterError('Username is already taken', Flux_RegisterError::USERNAME_ALREADY_TAKEN);
}
if (!Flux::config('AllowDuplicateEmails')) {
$sql = "SELECT email FROM {$this->loginDatabase}.login WHERE email = ? LIMIT 1";
$sth = $this->connection->getStatement($sql);
$sth->execute(array($email));
$res = $sth->fetch();
if ($res) {
throw new Flux_RegisterError('E-mail address is already in use', Flux_RegisterError::EMAIL_ADDRESS_IN_USE);
}
}
if ($this->config->getUseMD5()) {
$password = Flux::hashPassword($password);
}
$sql = "INSERT INTO {$this->loginDatabase}.login (userid, user_pass, email, sex, group_id, birthdate) VALUES (?, ?, ?, ?, ?, ?)";
$sth = $this->connection->getStatement($sql);
$res = $sth->execute(array($username, $password, $email, $gender, (int)$this->config->getGroupID(), date('Y-m-d', $birthdatestamp)));
if ($res) {
$idsth = $this->connection->getStatement("SELECT LAST_INSERT_ID() AS account_id");
$idsth->execute();
$idres = $idsth->fetch();
$createTable = Flux::config('FluxTables.AccountCreateTable');
$sql = "INSERT INTO {$this->loginDatabase}.{$createTable} (account_id, userid, user_pass, sex, email, reg_date, reg_ip, confirmed) ";
$sql .= "VALUES (?, ?, ?, ?, ?, NOW(), ?, 1)";
$sth = $this->connection->getStatement($sql);
$sth->execute(array($idres->account_id, $username, $password, $gender, $email, $_SERVER['REMOTE_ADDR']));
return $idres->account_id;
}
else {
return false;
}
}
/**
*
*/
public function temporarilyBan($bannedBy, $banReason, $accountID, $until)
{
$table = Flux::config('FluxTables.AccountBanTable');
$sql = "INSERT INTO {$this->loginDatabase}.$table (account_id, banned_by, ban_type, ban_until, ban_date, ban_reason) ";
$sql .= "VALUES (?, ?, 1, ?, NOW(), ?)";
$sth = $this->connection->getStatement($sql);
if ($sth->execute(array($accountID, $bannedBy, $until, $banReason))) {
$ts = strtotime($until);
$sql = "UPDATE {$this->loginDatabase}.login SET state = 0, unban_time = '$ts' WHERE account_id = ?";
$sth = $this->connection->getStatement($sql);
return $sth->execute(array($accountID));
}
else {
return false;
}
}
/**
*
*/
public function permanentlyBan($bannedBy, $banReason, $accountID)
{
$table = Flux::config('FluxTables.AccountBanTable');
$sql = "INSERT INTO {$this->loginDatabase}.$table (account_id, banned_by, ban_type, ban_until, ban_date, ban_reason) ";
$sql .= "VALUES (?, ?, 2, '9999-12-31 23:59:59', NOW(), ?)";
$sth = $this->connection->getStatement($sql);
if ($sth->execute(array($accountID, $bannedBy, $banReason))) {
$sql = "UPDATE {$this->loginDatabase}.login SET state = 5, unban_time = 0 WHERE account_id = ?";
$sth = $this->connection->getStatement($sql);
return $sth->execute(array($accountID));
}
else {
return false;
}
}
/**
*
*/
public function unban($unbannedBy, $unbanReason, $accountID)
{
$table = Flux::config('FluxTables.AccountBanTable');
$createTable = Flux::config('FluxTables.AccountCreateTable');
$sql = "INSERT INTO {$this->loginDatabase}.$table (account_id, banned_by, ban_type, ban_until, ban_date, ban_reason) ";
$sql .= "VALUES (?, ?, 0, '1000-01-01 00:00:00', NOW(), ?)";
$sth = $this->connection->getStatement($sql);
if ($sth->execute(array($accountID, $unbannedBy, $unbanReason))) {
$sql = "UPDATE {$this->loginDatabase}.$createTable SET confirmed = 1, confirm_expire = NULL WHERE account_id = ?";
$sth = $this->connection->getStatement($sql);
$sth->execute(array($accountID));
$sql = "UPDATE {$this->loginDatabase}.login SET state = 0, unban_time = 0 WHERE account_id = ?";
$sth = $this->connection->getStatement($sql);
return $sth->execute(array($accountID));
}
else {
return false;
}
}
/**
*
*/
public function getBanInfo($accountID)
{
$table = Flux::config('FluxTables.AccountBanTable');
$col = "$table.id, $table.account_id, $table.banned_by, $table.ban_type, ";
$col .= "$table.ban_until, $table.ban_date, $table.ban_reason, login.userid";
$sql = "SELECT $col FROM {$this->loginDatabase}.$table ";
$sql .= "LEFT OUTER JOIN {$this->loginDatabase}.login ON login.account_id = $table.banned_by ";
$sql .= "WHERE $table.account_id = ? ORDER BY $table.ban_date DESC ";
$sth = $this->connection->getStatement($sql);
$res = $sth->execute(array($accountID));
if ($res) {
$ban = $sth->fetchAll();
return $ban;
}
else {
return false;
}
}
/**
*
*/
public function addIpBan($bannedBy, $banReason, $unbanTime, $ipAddress)
{
$table = Flux::config('FluxTables.IpBanTable');
$sql = "INSERT INTO {$this->loginDatabase}.$table (ip_address, banned_by, ban_type, ban_until, ban_date, ban_reason) ";
$sql .= "VALUES (?, ?, 1, ?, NOW(), ?)";
$sth = $this->connection->getStatement($sql);
if ($sth->execute(array($ipAddress, $bannedBy, $unbanTime, $banReason))) {
$sql = "INSERT INTO {$this->loginDatabase}.ipbanlist (list, reason, rtime, btime) ";
$sql .= "VALUES (?, ?, ?, NOW())";
$sth = $this->connection->getStatement($sql);
return $sth->execute(array($ipAddress, $banReason, $unbanTime));
}
else {
return false;
}
}
/**
*
*/
public function removeIpBan($unbannedBy, $unbanReason, $ipAddress)
{
$table = Flux::config('FluxTables.IpBanTable');
$sql = "INSERT INTO {$this->loginDatabase}.$table (ip_address, banned_by, ban_type, ban_until, ban_date, ban_reason) ";
$sql .= "VALUES (?, ?, 0, '1000-01-01 00:00:00', NOW(), ?)";
$sth = $this->connection->getStatement($sql);
if ($sth->execute(array($ipAddress, $unbannedBy, $unbanReason))) {
$sql = "DELETE FROM {$this->loginDatabase}.ipbanlist WHERE list = ?";
$sth = $this->connection->getStatement($sql);
return $sth->execute(array($ipAddress));
}
else {
return false;
}
}
/**
*
*/
public function hasCreditsRecord($accountID)
{
$creditsTable = Flux::config('FluxTables.CreditsTable');
$sql = "SELECT COUNT(account_id) AS hasRecord FROM {$this->loginDatabase}.$creditsTable WHERE account_id = ?";
$sth = $this->connection->getStatement($sql);
$sth->execute(array($accountID));
if ($sth->fetch()->hasRecord) {
return true;
}
else {
return false;
}
}
/**
*
*/
public function depositCredits($targetAccountID, $credits, $donationAmount = null)
{
$sql = "SELECT COUNT(account_id) AS accountExists FROM {$this->loginDatabase}.login WHERE account_id = ?";
$sth = $this->connection->getStatement($sql);
if (!$sth->execute(array($targetAccountID)) || !$sth->fetch()->accountExists) {
return false; // Account doesn't exist.
}
$creditsTable = Flux::config('FluxTables.CreditsTable');
if (!$this->hasCreditsRecord($targetAccountID)) {
$fields = 'account_id, balance';
$values = '?, ?';
if (!is_null($donationAmount)) {
$fields .= ', last_donation_date, last_donation_amount';
$values .= ', NOW(), ?';
}
$sql = "INSERT INTO {$this->loginDatabase}.$creditsTable ($fields) VALUES ($values)";
$sth = $this->connection->getStatement($sql);
$vals = array($targetAccountID, $credits);
if (!is_null($donationAmount)) {
$vals[] = $donationAmount;
}
return $sth->execute($vals);
}
else {
$vals = array();
$sql = "UPDATE {$this->loginDatabase}.$creditsTable SET balance = balance + ? ";
if (!is_null($donationAmount)) {
$sql .= ", last_donation_date = NOW(), last_donation_amount = ? ";
}
$vals[] = $credits;
if (!is_null($donationAmount)) {
$vals[] = $donationAmount;
}
$vals[] = $targetAccountID;
$sql .= "WHERE account_id = ?";
$sth = $this->connection->getStatement($sql);
return $sth->execute($vals);
}
}
/**
*
*/
public function getPrefs($accountID, array $prefs = array())
{
$sql = "SELECT account_id FROM {$this->loginDatabase}.`login` WHERE account_id = ? LIMIT 1";
$sth = $this->connection->getStatement($sql);
if ($sth->execute(array($accountID)) && ($char=$sth->fetch())) {
$accountPrefsTable = Flux::config('FluxTables.AccountPrefsTable');
$pref = array();
$bind = array($accountID);
$sql = "SELECT name, value FROM {$this->loginDatabase}.$accountPrefsTable ";
$sql .= "WHERE account_id = ?";
if ($prefs) {
foreach ($prefs as $p) {
$pref[] = "name = ?";
$bind[] = $p;
}
$sql .= sprintf(' AND (%s)', implode(' OR ', $pref));
}
$sth = $this->connection->getStatement($sql);
if ($sth->execute($bind)) {
$prefsArray = array();
foreach ($sth->fetchAll() as $p) {
$prefsArray[$p->name] = $p->value;
}
return new Flux_Config($prefsArray);
}
else {
return false;
}
}
else {
return false;
}
}
/**
*
*/
public function setPrefs($accountID, array $prefsArray)
{
$sql = "SELECT account_id FROM {$this->loginDatabase}.`login` WHERE account_id = ? LIMIT 1";
$sth = $this->connection->getStatement($sql);
if ($sth->execute(array($accountID)) && ($char=$sth->fetch())) {
$accountPrefsTable = Flux::config('FluxTables.AccountPrefsTable');
$pref = array();
$bind = array($accountID);
$sql = "SELECT id, name, value FROM {$this->loginDatabase}.$accountPrefsTable ";
$sql .= "WHERE account_id = ?";
if ($prefsArray) {
foreach ($prefsArray as $prefName => $prefValue) {
$pref[] = "name = ?";
$bind[] = $prefName;
}
$sql .= sprintf(' AND (%s)', implode(' OR ', $pref));
}
$sth = $this->connection->getStatement($sql);
if ($sth->execute($bind)) {
$prefs = $sth->fetchAll();
$update = array();
$usql = "UPDATE {$this->loginDatabase}.$accountPrefsTable ";
$usql .= "SET value = ? WHERE id = ?";
$usth = $this->connection->getStatement($usql);
$isql = "INSERT INTO {$this->loginDatabase}.$accountPrefsTable ";
$isql .= "(account_id, name, value, create_date) ";
$isql .= "VALUES (?, ?, ?, NOW())";
$isth = $this->connection->getStatement($isql);
foreach ($prefs as $p) {
$update[$p->name] = $p->id;
}
foreach ($prefsArray as $pref => $value) {
if (array_key_exists($pref, $update)) {
$id = $update[$pref];
$usth->execute(array($value, $id));
}
else {
$isth->execute(array($accountID, $pref, $value));
}
}
return true;
}
else {
return false;
}
}
else {
return false;
}
}
/**
*
*/
public function getPref($accountID, $pref)
{
$prefs = $this->getPrefs($accountID, array($pref));
if ($prefs instanceOf Flux_Config) {
return $prefs->get($pref);
}
else {
return false;
}
}
/**
*
*/
public function setPref($accountID, $pref, $value)
{
return $this->setPrefs($accountID, array($pref => $value));
}
/**
*
*/
public function isIpBanned($ip = null)
{
if (is_null($ip)) {
$ip = $_SERVER['REMOTE_ADDR'];
}
$ip = trim($ip);
if (!preg_match('/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/', $ip, $m)) {
// Invalid IP.
return false;
}
$sql = "SELECT list FROM {$this->loginDatabase}.ipbanlist WHERE ";
$sql .= "rtime > NOW() AND (list = ? OR list = ? OR list = ? OR list = ?) LIMIT 1";
$sth = $this->connection->getStatement($sql);
$list = array(
sprintf('%u.*.*.*', $m[1]),
sprintf('%u.%u.*.*', $m[1], $m[2]),
sprintf('%u.%u.%u.*', $m[1], $m[2], $m[3]),
sprintf('%u.%u.%u.%u', $m[1], $m[2], $m[3], $m[4])
);
$sth->execute($list);
$ipban = $sth->fetch();
if ($ipban) {
return true;
}
else {
return false;
}
}
}
?>
================================================
FILE: lib/Flux/Mailer.php
================================================
pm = $pm = new PHPMailer();
$this->errLog = self::$errLog;
$this->log = self::$log;
if (Flux::config('MailerUseSMTP')) {
$pm->IsSMTP();
if (is_array($hosts=Flux::config('MailerSMTPHosts'))) {
$hosts = implode(';', $hosts);
}
$pm->Host = $hosts;
if ($user=Flux::config('MailerSMTPUsername')) {
$pm->SMTPAuth = true;
if (Flux::config('MailerSMTPUseTLS')) {
$pm->SMTPSecure = 'tls';
}
if (Flux::config('MailerSMTPUseSSL')) {
$pm->SMTPSecure = 'ssl';
}
if ($port=Flux::config('MailerSMTPPort')) {
$pm->Port = (int)$port;
}
$pm->Username = $user;
if ($pass=Flux::config('MailerSMTPPassword')) {
$pm->Password = $pass;
}
}
}
// From address.
$pm->setFrom(Flux::config('MailerFromAddress'), Flux::config('MailerFromName'));
}
public function send($recipient, $subject, $template, array $templateVars = array())
{
if (array_key_exists('_ignoreTemplate', $templateVars) && $templateVars['_ignoreTemplate']) {
$content = $template;
} else {
$templatePath = FLUX_DATA_DIR."/templates/$template.php";
if (!file_exists($templatePath)) {
return false;
}
$find = array();
$repl = array();
foreach ($templateVars as $key => $value) {
$find[] = '{'.$key.'}';
$repl[] = $value;
}
ob_start();
include $templatePath;
$content = ob_get_clean();
if (!empty($find) && !empty($repl)) {
$content = str_replace($find, $repl, $content);
}
}
$this->pm->isHTML(true);
$this->pm->CharSet = 'UTF-8';
$this->pm->AddAddress($recipient);
$this->pm->Subject = $subject;
$this->pm->msgHTML($content);
if ($sent=$this->pm->Send()) {
self::$log->puts("sent e-mail -- Recipient: $recipient, Subject: $subject");
}
else {
self::$errLog->puts("{$this->pm->ErrorInfo} (while attempting -- Recipient: $recipient, Subject: $subject)");
}
return $sent;
}
}
?>
================================================
FILE: lib/Flux/MapServer.php
================================================
================================================
FILE: lib/Flux/Paginator.php
================================================
requestURI = $requestURI;
$perPage = Flux::config('ResultsPerPage');
if (!$perPage) {
$perPage = 20;
}
$pagesToShow = Flux::config('PagesToShow');
if (!$pagesToShow) {
$pagesToShow = 10;
}
$showSinglePage = (bool)Flux::config('ShowSinglePage');
$options = array_merge(
array(
'showSinglePage' => $showSinglePage,
'perPage' => $perPage,
'pagesToShow' => $pagesToShow,
'pageVariable' => 'p',
'pageSeparator' => '|'),
$options
);
$this->total = (int)$total;
$this->showSinglePage = $options['showSinglePage'];
$this->perPage = $options['perPage'];
$this->pagesToShow = $options['pagesToShow'];
$this->pageVariable = $options['pageVariable'];
$this->pageSeparator = $options['pageSeparator'];
$this->currentPage = isset($_GET[$this->pageVariable]) && $_GET[$this->pageVariable] > 0 ? $_GET[$this->pageVariable] : 1;
$this->calculatePages();
}
/**
* Calculate the number of pages.
*
* @access private
*/
private function calculatePages()
{
$this->numberOfPages = (int)ceil($this->total / $this->perPage);
}
/**
* Get an SQL query with the "LIMIT offset,num" and appropriate "ORDER BY"
* strings appended to the end.
*
* @param string $sql
* @return string
* @access public
*/
public function getSQL($sql)
{
$orderBy = false;
foreach ($this->sortableColumns as $column => $value) {
if (strpos($column, '.') !== false) {
list ($table, $column) = explode('.', $column, 2);
$param = "{$table}_{$column}_order";
$columnName = "`{$table}`.`{$column}`";
}
else {
$table = false;
$param = "{$column}_order";
$columnName = "`$column`";
}
$sortValues = array('ASC', 'DESC', 'NONE');
// First, check if a GET parameter was passed for this column.
if (isset($_GET[$param]) && in_array(strtoupper($_GET[$param]), $sortValues)) {
$value = $_GET[$param];
}
// Check again just in case we're working with the default here.
if (!is_null($value) && in_array( ($value=strtoupper($value)), $sortValues ) && $value != 'NONE') {
$this->currentSortOrder[str_replace("`", "", $columnName)] = $value;
if (!$orderBy) {
$sql .= ' ORDER BY';
$orderBy = true;
}
if ($value == 'ASC') {
$sql .= " (CASE WHEN $columnName IS NULL THEN 1 ELSE 0 END) ASC, $columnName ASC,";
}
else {
$sql .= " $columnName $value,";
}
}
}
if ($orderBy) {
$sql = rtrim($sql, ',');
}
$offset = ($this->perPage * $this->currentPage) - $this->perPage;
return "$sql LIMIT $offset,{$this->perPage}";
}
/**
* Generate some basic HTML which creates a list of page numbers. Will
* return an empty string if DisplaySinglePages config is set to false.
*
* @return string
* @access public
*/
public function getHTML()
{
if (!Flux::config('DisplaySinglePages') && $this->numberOfPages === 1) {
return '';
}
$pages = array();
$start = (floor(($this->currentPage - 1) / $this->pagesToShow) * $this->pagesToShow) + 1;
$end = $start + $this->pagesToShow + 1;
if ($end > $this->numberOfPages) {
$end = $this->numberOfPages + 1;
}
else {
$end = $end - 1;
}
$hasPrev = $start > 1;
$hasNext = $end < $this->numberOfPages;
for ($i = $start; $i < $end; ++$i) {
$request = $this->getPageURI($i);
if ($i == $this->currentPage) {
$pages[] = sprintf(
'%d',
$i, $i
);
}
else {
$pages[] = sprintf(
'%d',
$request, $i, $i
);
}
}
if ($hasPrev) {
array_unshift($pages, sprintf('Prev. ', $this->getPageURI($start - 1), $start - 1));
}
if ($hasNext) {
array_push($pages, sprintf(' Next', $this->getPageURI($end), $end));
}
$links = sprintf('
%s
', implode(" {$this->pageSeparator} ", $pages))."\n";
if (Flux::config('ShowPageJump') && $this->numberOfPages > Flux::config('PageJumpMinimumPages')) {
// This is some tricky shit. Don't even attempt to understand it =(
// Page jumping is entirely JavaScript dependent.
$pageVar = preg_quote($this->pageVariable);
$event = "location.href='".$this->getPageURI(0)."'";
$event = preg_replace("/$pageVar=0/", "{$this->pageVariable}='+this.value+'", $event);
$jump = '';
$jump = sprintf($jump, $event);
$links .= sprintf('
%s
', $jump);
}
if (!$this->showSinglePage && $this->numberOfPages === 1) {
return null;
}
else {
return $links;
}
}
/**
* Create a link to the current request with a different page number.
*
* @param int $pageNumber
* @return string
* @access protected
*/
protected function getPageURI($pageNumber)
{
$request = preg_replace('/(\?.*)$/', '', $this->requestURI);
$qString = $_SERVER['QUERY_STRING'];
$pageVar = preg_quote($this->pageVariable);
$pageNum = (int)$pageNumber;
$qStringVars = array();
$qStringLines = preg_split('/&/', $qString, -1, PREG_SPLIT_NO_EMPTY);
foreach ($qStringLines as $qStringVar) {
if (strpos($qStringVar, '=') !== false) {
list($qStringKey, $qStringVal) = explode('=', $qStringVar, 2);
$qStringVars[$qStringKey] = $qStringVal;
}
}
$qStringVars[$pageVar] = $pageNum;
$qStringLines = array();
foreach ($qStringVars as $qStringKey => $qStringVal) {
$qStringLines[] = sprintf('%s=%s', $qStringKey, $qStringVal);
}
return sprintf('%s?%s', $request, implode('&', $qStringLines));
}
/**
* Specify an array (or a string single column name) of columns that are
* sortable by the paginator's features.
*
* @param array $columns
* @return array
* @access public
*/
public function setSortableColumns($columns)
{
if (!is_array($columns)) {
$columns = array($columns);
}
foreach ($columns as $key => $column) {
if (!is_numeric($key)) {
$value = $column;
$column = $key;
}
else {
$value = null;
}
$this->sortableColumns[$column] = $value;
}
return $this->sortableColumns;
}
/**
* Get an HTML anchor which automatically links to the current request
* based on current sorting conditions and sets ascending/descending
* sorting parameters accordingly.
*
* @param string $column
* @param string $name
* @return string
* @access public
*/
public function sortableColumn($column, $name = null)
{
if (!$name) {
$name = $column;
}
if (!array_key_exists($column, $this->sortableColumns)) {
return htmlspecialchars($name);
}
else {
if (strpos($column, '.') !== false) {
list ($_table, $_column) = explode('.', $column, 2);
$param = "{$_table}_{$_column}_order";
}
else {
$param = "{$column}_order";
}
$order = 'asc';
$format = '%s';
$name = htmlspecialchars($name);
$request = $_SERVER['REQUEST_URI'];
if (isset($this->currentSortOrder[$column])) {
switch (strtolower($this->currentSortOrder[$column])) {
case 'asc':
$order = 'desc';
$name .= Flux::config('ColumnSortAscending');
break;
case 'desc':
$order = is_null($this->sortableColumns[$column]) ? false : 'none';
$name .= Flux::config('ColumnSortDescending');
break;
default:
$order = 'asc';
break;
}
}
if ($order) {
$value = "$param=$order";
if (preg_match("/$param=(\w*)/", $request)) {
$request = preg_replace("/$param=(\w*)/", $value, $request);
}
elseif (empty($_SERVER['QUERY_STRING'])) {
$request = "$request?$value";
}
else {
$request = "$request&$value";
}
return sprintf($format, $request, $name);
}
else {
$request = rtrim(preg_replace("%(?:(\?)$param=(?:\w*)&?|&?$param=(?:\w*))%", '$1', $request), '?');
return sprintf($format, $request, $name);
}
}
}
/**
*
*/
public function infoText()
{
$currPage = $this->currentPage;
$results = $this->perPage;
$infoText = sprintf(
Flux::message('FoundSearchResults'),
$this->total, $this->numberOfPages, ($currPage*$results-($results - 1)), $currPage * $results < $this->total ? ($currPage*$results) : ($this->total)
);
return sprintf('
%s
', $infoText);
}
}
?>
================================================
FILE: lib/Flux/PaymentNotifyRequest.php
================================================
ppLogFile = new Flux_LogFile(FLUX_DATA_DIR.'/logs/paypal.log');
$this->ppServer = Flux::config('PayPalIpnUrl');
$this->myBusinessEmail = Flux::config('PayPalBusinessEmail');
$this->myCurrencyCode = strtoupper(Flux::config('DonationCurrency'));
$this->ipnVariables = new Flux_Config($ipnPostVars);
$this->txnLogTable = Flux::config('FluxTables.TransactionTable');
$this->creditsTable = Flux::config('FluxTables.CreditsTable');
}
/**
* Log to PayPal log file. Works like printf().
*
* @param string $format
* @param mixed ...
* @return string
* @access protected
*/
protected function logPayPal()
{
$args = func_get_args();
$func = array($this->ppLogFile, 'puts');
return call_user_func_array($func, $args);
}
/**
* Get user IP.
* Checks if CloudFlare used to get real IP.
*
* @access public
*/
protected function fetchIP()
{
$alt_ip = $_SERVER['REMOTE_ADDR'];
if (isset($_SERVER['HTTP_X_REAL_IP'])) {
$alt_ip = $_SERVER['HTTP_X_REAL_IP'];
} elseif (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
$alt_ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
} elseif (isset($_SERVER['HTTP_CLIENT_IP'])) {
$alt_ip = $_SERVER['HTTP_CLIENT_IP'];
} elseif (isset($_SERVER['HTTP_CF_CONNECTING_IP'])) {
$alt_ip = $_SERVER['HTTP_CF_CONNECTING_IP'];
}
return $alt_ip;
}
/**
* Process transaction.
*
* @access public
*/
public function process()
{
$allowed_hosts = Flux::config('PayPalAllowedHosts')->toArray();
$received_from = gethostbyaddr($this->fetchIP());
$this->logPayPal('Received notification from %s (%s)', $this->fetchIP(), $received_from);
if ((in_array($received_from, $allowed_hosts) && $this->verify()) || $this->verifyiprange($received_from)) {
$this->logPayPal('Proceeding to validate the authenticity of the transaction...');
$accountEmails = Flux::config('PayPalReceiverEmails');
$accountEmails = array_merge(array($this->myBusinessEmail), $accountEmails->toArray());
$receiverEmail = $this->ipnVariables->get('receiver_email');
$transactionID = $this->ipnVariables->get('txn_id');
$paymentStatus = $this->ipnVariables->get('payment_status');
$payerEmail = $this->ipnVariables->get('payer_email');
$currencyCode = strtoupper(substr($this->ipnVariables->get('mc_currency'), 0, 3));
$trusted = true;
// Identify transaction number.
$this->logPayPal('Transaction identified as %s.', $transactionID);
if (!in_array($receiverEmail, $accountEmails)) {
$this->logPayPal('Receiver e-mail (%s) is not recognized, unauthorized to continue.', $receiverEmail);
}
else {
$customArray = @unserialize(base64_decode((string)$this->ipnVariables->get('custom')));
$customArray = $customArray && is_array($customArray) ? $customArray : array();
$customData = new Flux_Config($customArray);
$accountID = $customData->get('account_id');
$serverName = $customData->get('server_name');
if ($currencyCode != $this->myCurrencyCode) {
$this->logPayPal('Transaction currency not exchangeable, accepting anyways. (recv: %s, expected: %s)',
$currencyCode, $this->myCurrencyCode);
$exchangeableCurrency = false;
}
else {
$exchangeableCurrency = true;
}
// How much was received? (and in what currency?)
$this->logPayPal('Received %s (%s).', $this->ipnVariables->get('mc_gross'), $currencyCode);
// How much will be deposited?
$settleAmount = $this->ipnVariables->get('settle_amount');
$settleCurrency = $this->ipnVariables->get('settle_currency');
if ($settleAmount && $settleCurrency) {
$this->logPayPal('Deposited into PayPal account: %s %s.', $settleAmount, $settleCurrency);
}
// Let's see where the donation credits should go to.
$this->logPayPal('Game server name: %s, account ID: %s',
($serverName ? $serverName : '(absent)'), ($accountID ? $accountID : '(absent)'));
if (!$accountID || !$serverName) {
$this->logPayPal('Account ID and/or game server name absent, cannot exchange for credits.');
}
elseif ($this->ipnVariables->get('txn_type') != 'web_accept') {
$this->logPayPal('Transaction type is not web_accept, amount will not be exchanged for credits.');
}
elseif (!($servGroup = Flux::getServerGroupByName($serverName))) {
$this->logPayPal('Unknown game server "%s", cannot process donation for credits.', $serverName);
}
if ($paymentStatus == 'Completed') {
$this->logPayPal('Payment for txn_id#%s has been completed.', $transactionID);
if ($servGroup && $exchangeableCurrency) {
$sql = "SELECT COUNT(account_id) AS acc_id_count FROM {$servGroup->loginDatabase}.login WHERE sex != 'S' AND group_id >= 0 AND account_id = ?";
$sth = $servGroup->connection->getStatement($sql);
$sth->execute(array($accountID));
$res = $sth->fetch();
if (!$res) {
$this->logPayPal('Unknown account #%s on server %s, cannot exchange for credits.', $accountID, $serverName);
}
else {
if (!$servGroup->loginServer->hasCreditsRecord($accountID)) {
$this->logPayPal('Identified as first-time donation to the server from this account.');
}
$amount = (float)$this->ipnVariables->get('mc_gross');
$minimum = (float)Flux::config('MinDonationAmount');
if ($amount >= $minimum) {
$trustTable = Flux::config('FluxTables.DonationTrustTable');
$holdHours = +(int)Flux::config('HoldUntrustedAccount');
if ($holdHours) {
$sql = "SELECT account_id, email FROM {$servGroup->loginDatabase}.$trustTable WHERE account_id = ? AND email = ? LIMIT 1";
$sth = $servGroup->connection->getStatement($sql);
$sth->execute(array($accountID, $payerEmail));
$res = $sth->fetch();
if ($res && $res->account_id) {
$this->logPayPal('Account ID and e-mail are trusted.');
$trusted = true;
}
else {
$trusted = false;
}
}
$rate = Flux::config('CreditExchangeRate');
$credits = floor($amount / $rate);
if ($trusted) {
$sql = "SELECT * FROM {$servGroup->loginDatabase}.{$this->creditsTable} WHERE account_id = ?";
$sth = $servGroup->connection->getStatement($sql);
$sth->execute(array($accountID));
$acc = $sth->fetch();
$this->logPayPal('Updating account credit balance from %s to %s', (int)$acc->balance, $acc->balance + $credits);
$res = $servGroup->loginServer->depositCredits($accountID, $credits, $amount);
if ($res) {
$this->logPayPal('Deposited credits.');
}
else {
$this->logPayPal('Failed to deposit credits.');
}
}
else {
$this->logPayPal('Account/e-mail is not trusted, holding donation credits for %d hours.', $holdHours);
}
}
else {
$this->logPayPal('User has donated less than the configured minimum, not exchanging credits.');
}
}
}
}
else {
$this->logPayPal('Incomplete payment status: %s (exchanging for credits will not take place)', $paymentStatus);
$banStatuses = Flux::config('BanPaymentStatuses');
if ($banStatuses instanceOf Flux_Config) {
$banStatuses = $banStatuses->toArray();
}
else {
$banStatuses = array();
}
$pymntStatus = strtolower($paymentStatus);
$banStatuses = array_map('strtolower', $banStatuses);
if (in_array($pymntStatus, $banStatuses)) {
$this->logPayPal('Auto-ban payment status detected: %s', $paymentStatus);
if ($servGroup && $serverName && $accountID) {
$this->logPayPal('Banning account! (serv: %s, account_id: %s)', $serverName, $accountID);
$servGroup->loginServer->permanentlyBan(
null, "Banned for invalid payment status: $paymentStatus",
$accountID
);
}
else {
$this->logPayPal("Couldn't ban account, it's unknown.");
}
}
}
if (!$servGroup) {
foreach (Flux::$loginAthenaGroupRegistry as $servGroup) {
$this->logToPayPalTable($servGroup, $accountID, $serverName, $trusted);
}
}
else {
if (empty($credits)) {
$credits = 0;
}
$this->logToPayPalTable($servGroup, $accountID, $serverName, $trusted, $credits);
}
$this->logPayPal('Saving transaction details for %s...', $transactionID);
if ($logFile=$this->saveDetailsToFile()) {
$this->logPayPal('Saved transaction details for %s to: %s', $transactionID, $logFile);
}
else {
$this->logPayPal('Failed to save transaction details for %s to file.', $transactionID);
}
$this->logPayPal('Done processing %s.', $transactionID);
}
}
else {
$this->logPayPal('Transaction invalid, aborting.');
if(!in_array($received_from, $allowed_hosts) && Flux::config('PaypalHackNotify')){
require_once 'Flux/Mailer.php';
$customArray = @unserialize(base64_decode((string)$this->ipnVariables->get('custom')));
$customArray = $customArray && is_array($customArray) ? $customArray : array();
$customData = new Flux_Config($customArray);
$accountID = $customData->get('account_id');
$serverName = $customData->get('server_name');
$mail = new Flux_Mailer();
$tmpl = "
";
$accountEmails = Flux::config('PayPalReceiverEmails');
$accountEmails = array_merge(array($this->myBusinessEmail), $accountEmails->toArray());
foreach($accountEmails as $email) {
$sent = $mail->send($email, '['.Flux::config('SiteTitle').'] Paypal hack', $tmpl, array('_ignoreTemplate' => true));
}
$this->logPayPal('Hack detected!');
}
}
return false;
}
/**
* Translate the IPN variables into a query string for use in a POST
* request.
*
* @return string
* @access private
*/
private function ipnVarsToQueryString()
{
$ipnVars = $this->ipnVariables->toArray();
$qString = '';
foreach ($ipnVars as $key => $value) {
$qString .= sprintf('&%s=%s', $key, urlencode($value));
}
$qString = ltrim($qString, '&');
return $qString;
}
/**
* Verify IPN variables against PayPal server.
*
* Updated to comply with changes being implemented Feb 1, 2013
* https://www.x.com/node/320404
*
* @return bool True if verified, false if not.
* @access private
*/
private function verify()
{
$qString = 'cmd=_notify-validate&'.$this->ipnVarsToQueryString();
$request = "POST /cgi-bin/webscr HTTP/1.1\r\n";
$request .= "Content-Type: application/x-www-form-urlencoded\r\n";
$request .= 'Content-Length: '.strlen($qString)."\r\n";
$request .= 'Host: '.$this->ppServer."\r\n";
$request .= "Connection: close\r\n\r\n";
$request .= $qString;
$this->logPayPal('Query string: %s', $qString);
$this->logPayPal('Establishing connection to PayPal server at %s:443...', $this->ppServer);
$fp = @fsockopen('ssl://'.$this->ppServer, 443, $errno, $errstr, 20);
if (!$fp) {
$this->logPayPal("Failed to connect to PayPal server: [%d] %s", $errno, $errstr);
return false;
}
else {
$this->logPayPal('Connected. Sending request back to PayPal...');
// Send POST request just as PayPal sent it.
$this->logPayPal('Sent %d bytes of transaction data. Request size: %d bytes.', strlen($qString), fputs($fp, $request));
$this->logPayPal('Reading back response from PayPal...');
// Read until body starts
while (!feof($fp) && ($line = trim(fgets($fp))) != '');
$line = '';
// Read until EOF, contains VERIFIED or INVALID.
while (!feof($fp)) {
$line .= strtoupper(trim(fgets($fp)));
}
// Close connection.
fclose($fp);
// Check verification status of the notify request.
if (strpos($line, 'VERIFIED') !== false) {
$this->logPayPal('Notification verified. (recv: %s)', $line);
$this->txnIsValid = true;
return true;
}
else {
$this->logPayPal('Notification failed to verify. (recv: %s)', $line);
return false;
}
}
}
/*
*/
private function verifyiprange($received_from)
{
$allowed_hosts = Flux::config('PayPalAllowedHosts')->toArray();
$ip_long = ip2long ( $received_from );
for ($i = 0; $i < 72; $i++)
{
if(strpos($allowed_hosts[$i], '/') !== false) {
$ip_arr = explode ( '/' , $allowed_hosts[$i] );
$network_long = ip2long ( $ip_arr[0] );
$x = ip2long ( $ip_arr [1]);
$mask = long2ip ( $x ) == $ip_arr [ 1 ] ? $x : 0xffffffff << ( 32 - $ip_arr [ 1 ]);
if(( $ip_long & $mask ) == ( $network_long & $mask ))
return true;
} else {
if($allowed_hosts[$i] == $received_from)
return true;
}
}
return false;
}
/**
* Save the transaction details to disk in the file name format of:
* data/logs/transactions/TXN_TYPE/PAYMENT_STATUS.log
*
* @return string File name
* @access private
*/
private function saveDetailsToFile()
{
if ($this->txnIsValid) {
$logDir1 = realpath(FLUX_DATA_DIR.'/logs/transactions');
$logDir2 = $logDir1.'/'.$this->ipnVariables->get('txn_type');
$logDir3 = $logDir2.'/'.$this->ipnVariables->get('payment_status');
$logFile = $logDir3.'/'.$this->ipnVariables->get('txn_id').'.log.php';
if (!is_dir($logDir2)) {
mkdir($logDir2, 0600);
}
if (!is_dir($logDir3)) {
mkdir($logDir3, 0600);
}
$fp = fopen($logFile, 'w');
if ($fp) {
fwrite($fp, "\n");
foreach ($this->ipnVariables->toArray() as $key => $value) {
fwrite($fp, "$key: $value\n");
}
fclose($fp);
return $logFile;
}
}
return false;
}
/**
* Log the transaction details into the flux_paypal_transactions table.
*
* @param Flux_LoginAthenaGroup $servGroup
* @param string $accountID
* @param string $serverName
* @access private
*/
private function logToPayPalTable(Flux_LoginAthenaGroup $servGroup, $accountID, $serverName, $trusted, $credits = 0)
{
if ($this->txnIsValid) {
$holdUntil = null;
if (!$trusted) {
$email = $this->ipnVariables->get('payer_email');
$sql = "SELECT hold_until FROM {$servGroup->loginDatabase}.{$this->txnLogTable} ";
$sql .= "WHERE account_id = ? AND payer_email = ? AND hold_until > NOW() AND payment_status = 'Completed' LIMIT 1";
$sth = $sth = $servGroup->connection->getStatement($sql);
$sth->execute(array($accountID, $email));
$row = $sth->fetch();
if ($row && $row->hold_until) {
$holdUntil = $row->hold_until;
}
else {
$hours = +(int)Flux::config('HoldUntrustedAccount');
$holdUntil = date('Y-m-d H:i:s', time()+($hours*60*60));
}
}
$this->logPayPal('Saving transaction details to PayPal transactions table...');
$sql = "
INSERT INTO {$servGroup->loginDatabase}.{$this->txnLogTable} (
account_id,
server_name,
credits,
receiver_email,
item_name,
item_number,
quantity,
payment_status,
pending_reason,
payment_date,
mc_gross,
mc_fee,
tax,
mc_currency,
parent_txn_id,
txn_id,
txn_type,
first_name,
last_name,
address_street,
address_city,
address_state,
address_zip,
address_country,
address_status,
payer_email,
payer_status,
payment_type,
notify_version,
verify_sign,
referrer_id,
process_date,
hold_until
) VALUES (
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW(),
?
)
";
$var = $this->ipnVariables;
$sth = $servGroup->connection->getStatement($sql);
$ret = $sth->execute(array(
$accountID,
$serverName,
$credits,
$var->get('receiver_email'),
$var->get('item_name'),
$var->get('item_number'),
$var->get('quantity'),
$var->get('payment_status'),
$var->get('pending_reason'),
$var->get('payment_date'),
$var->get('mc_gross'),
$var->get('mc_fee'),
$var->get('tax'),
$var->get('mc_currency'),
$var->get('parent_txn_id'),
$var->get('txn_id'),
$var->get('txn_type'),
$var->get('first_name'),
$var->get('last_name'),
$var->get('address_street'),
$var->get('address_city'),
$var->get('address_state'),
$var->get('address_zip'),
$var->get('address_country'),
$var->get('address_status'),
$var->get('payer_email'),
$var->get('payer_status'),
$var->get('payment_type'),
$var->get('notify_version'),
$var->get('verify_sign'),
$var->get('receiver_id'),
$holdUntil
));
if ($ret) {
if (!trim($serverName)) {
$serverName = '(unknown)';
}
$this->logPayPal('Stored information in PayPal transactions table for server %s.', $serverName);
}
else {
$errorInfo = implode('/', $sth->errorInfo());
$this->logPayPal('Failed to save information in PayPal transactions table. (%s)', $errorInfo);
}
}
}
}
?>
================================================
FILE: lib/Flux/PermissionError.php
================================================
================================================
FILE: lib/Flux/RegisterError.php
================================================
================================================
FILE: lib/Flux/SessionData.php
================================================
sessionData = &$sessionData;
if ($logout) {
$this->logout();
}
else {
$this->initialize();
}
}
/**
* Initialize session data.
*
* @param bool $force
* @return bool
* @access private
*/
private function initialize($force = false)
{
$keysToInit = array('username', 'serverName', 'athenaServerName', 'securityCode', 'theme');
foreach ($keysToInit as $key) {
if ($force || !$this->{$key}) {
$method = ucfirst($key);
$method = "set{$method}Data";
$this->$method(null);
}
}
$loggedIn = true;
if (!$this->username) {
$loggedIn = false;
$cfgAthenaServerName = Flux::config('DefaultCharMapServer');
$cfgLoginAthenaGroup = Flux::config('DefaultLoginGroup');
if (Flux::getServerGroupByName($cfgLoginAthenaGroup)){
$this->setServerNameData($cfgLoginAthenaGroup);
}
else {
$defaultServerName = current(array_keys(Flux::$loginAthenaGroupRegistry));
$this->setServerNameData($defaultServerName);
}
}
if ($this->serverName && ($this->loginAthenaGroup = Flux::getServerGroupByName($this->serverName))) {
$this->loginServer = $this->loginAthenaGroup->loginServer;
if (!$loggedIn && $cfgAthenaServerName && $this->getAthenaServer($cfgAthenaServerName)) {
$this->setAthenaServerNameData($cfgAthenaServerName);
}
if (!$this->athenaServerName || ((!$loggedIn && !$this->getAthenaServer($cfgAthenaServerName)) || !$this->getAthenaServer($this->athenaServerName))) {
$this->setAthenaServerNameData(current($this->getAthenaServerNames()));
}
}
// Get new account data every request.
if ($this->loginAthenaGroup && $this->username && ($account = $this->getAccount($this->loginAthenaGroup, $this->username))) {
$this->account = $account;
$this->account->group_level = AccountLevel::getGroupLevel($account->group_id);
// Automatically log out of account when detected as banned.
$permBan = ($account->state == 5 && !Flux::config('AllowPermBanLogin'));
$tempBan = (($account->unban_time > 0 && $account->unban_time < time()) && !Flux::config('AllowTempBanLogin'));
if ($permBan || $tempBan) {
$this->logout();
}
}
else {
$this->account = new Flux_DataObject(null, array('group_level' => AccountLevel::UNAUTH));
}
if (!is_array($this->cart)) {
$this->setCartData(array());
}
if ($this->account->account_id && $this->loginAthenaGroup) {
if (!array_key_exists($this->loginAthenaGroup->serverName, $this->cart)) {
$this->cart[$this->loginAthenaGroup->serverName] = array();
}
foreach ($this->getAthenaServerNames() as $athenaServerName) {
$athenaServer = $this->getAthenaServer($athenaServerName);
$cartArray = &$this->cart[$this->loginAthenaGroup->serverName];
$accountID = $this->account->account_id;
if (!array_key_exists($accountID, $cartArray)) {
$cartArray[$accountID] = array();
}
if (!array_key_exists($athenaServerName, $cartArray[$accountID])) {
$cartArray[$accountID][$athenaServerName] = new Flux_ItemShop_Cart();
}
$cartArray[$accountID][$athenaServerName]->setAccount($this->account);
$athenaServer->setCart($cartArray[$accountID][$athenaServerName]);
}
}
if (!$this->theme || $this->theme === 'installer') { // always update if coming from installer
$this->setThemeData(Flux::config('ThemeName.0'));
}
return true;
}
/**
* Log current user out.
*
* @return bool
* @access public
*/
public function logout()
{
$this->loginAthenaGroup = null;
$this->loginServer = null;
return $this->initialize(true);
}
public function __call($method, $args)
{
if (count($args) && preg_match('/set(.+?)Data/', $method, $m)) {
$arg = current($args);
$meth = $m[1];
$meth[0] = strtolower($meth[0]);
if (array_key_exists($meth, $this->dataFilters)) {
foreach ($this->dataFilters[$meth] as $callback) {
$arg = call_user_func($callback, $arg);
}
}
$this->sessionData[$meth] = $arg;
}
}
public function &__get($prop)
{
$value = null;
if (array_key_exists($prop, $this->sessionData)) {
$value = &$this->sessionData[$prop];
}
return $value;
}
/**
* Set session data.
*
* @param array $keys Session keys to be affected.
* @param mixed $value Value to be assigned to all specified keys.
* @return mixed whatever was set
* @access public
*/
public function setData(array $keys, $value)
{
foreach ($keys as $key) {
$key = ucfirst($key);
$this->{"set{$key}Data"}($value);
}
return $value;
}
/**
* Add a session data setter filter.
*
* @param string $key Which session key
* @param string $callback Function callback.
* @return string Callback
* @access public
*/
public function addDataFilter($key, $callback)
{
if (!array_key_exists($key, $this->dataFilters)) {
$this->dataFilters[$key] = array();
}
$this->dataFilters[$key][] = $callback;
return $callback;
}
/**
* Checks whether the current user is logged in.
*/
public function isLoggedIn()
{
return $this->account->group_level >= AccountLevel::NORMAL;
}
/**
* Check securityCode from user with $this->securityCode or reCaptcha
* @param $securityCode Code from user
* @param $recaptcha True if check using recaptcha
* @return true on success and false on failure
*/
public function checkSecurityCode($securityCode, $recaptcha = false) {
if ($recaptcha) {
if (Flux::config('ReCaptchaPrivateKey') && Flux::config('ReCaptchaPublicKey')) {
$responseKeys = array();
if (isset($_POST['g-recaptcha-response']) && $_POST['g-recaptcha-response'] != "") {
$response = file_get_contents("https://www.google.com/recaptcha/api/siteverify?secret=".Flux::config('ReCaptchaPrivateKey')."&response=".$_POST['g-recaptcha-response']."&remoteip=".$_SERVER['REMOTE_ADDR']);
$responseKeys = json_decode($response,true);
}
if (count($responseKeys) && intval($responseKeys["success"]) == 1) {
return true;
}
}
}
else if ($securityCode && !empty($securityCode) && strtolower($securityCode) == strtolower($this->securityCode)) {
return true;
}
return false;
}
/**
* User login.
*
* @param string $server Server name
* @param string $username
* @param string $password
* @throws Flux_LoginError
* @access public
*/
public function login($server, $username, $password, $securityCode = null)
{
$loginAthenaGroup = Flux::getServerGroupByName($server);
if (!$loginAthenaGroup) {
throw new Flux_LoginError('Invalid server.', Flux_LoginError::INVALID_SERVER);
}
if ($loginAthenaGroup->loginServer->isIpBanned() && !Flux::config('AllowIpBanLogin')) {
throw new Flux_LoginError('IP address is banned', Flux_LoginError::IPBANNED);
}
if (Flux::config('UseLoginCaptcha') && !self::checkSecurityCode($securityCode, Flux::config('EnableReCaptcha'))) {
throw new Flux_LoginError('Invalid security code', Flux_LoginError::INVALID_SECURITY_CODE);
}
if (!$loginAthenaGroup->isAuth($username, $password)) {
throw new Flux_LoginError('Invalid login', Flux_LoginError::INVALID_LOGIN);
}
$creditsTable = Flux::config('FluxTables.CreditsTable');
$creditColumns = 'credits.balance, credits.last_donation_date, credits.last_donation_amount';
$sql = "SELECT login.*, {$creditColumns} FROM {$loginAthenaGroup->loginDatabase}.login ";
$sql .= "LEFT OUTER JOIN {$loginAthenaGroup->loginDatabase}.{$creditsTable} AS credits ON login.account_id = credits.account_id ";
$sql .= "WHERE login.sex != 'S' AND login.group_id >= 0 AND login.userid = ? LIMIT 1";
$smt = $loginAthenaGroup->connection->getStatement($sql);
$res = $smt->execute(array($username));
if ($res && ($row = $smt->fetch())) {
if ($row->unban_time > 0) {
if (time() >= $row->unban_time) {
$row->unban_time = 0;
$sql = "UPDATE {$loginAthenaGroup->loginDatabase}.login SET unban_time = 0 WHERE account_id = ?";
$sth = $loginAthenaGroup->connection->getStatement($sql);
$sth->execute(array($row->account_id));
}
elseif (!Flux::config('AllowTempBanLogin')) {
throw new Flux_LoginError('Temporarily banned', Flux_LoginError::BANNED);
}
}
if ($row->state == 5) {
$createTable = Flux::config('FluxTables.AccountCreateTable');
$sql = "SELECT id FROM {$loginAthenaGroup->loginDatabase}.$createTable ";
$sql .= "WHERE account_id = ? AND confirmed = 0";
$sth = $loginAthenaGroup->connection->getStatement($sql);
$sth->execute(array($row->account_id));
$row2 = $sth->fetch();
if ($row2 && $row2->id) {
throw new Flux_LoginError('Pending confirmation', Flux_LoginError::PENDING_CONFIRMATION);
}
}
if (!Flux::config('AllowPermBanLogin') && $row->state == 5) {
throw new Flux_LoginError('Permanently banned', Flux_LoginError::PERMABANNED);
}
$this->setServerNameData($server);
$this->setUsernameData($username);
$this->initialize(false);
}
else {
$message = "Unexpected error during login.\n";
$message .= 'PDO error info, if any: '.print_r($smt->errorInfo(), true);
throw new Flux_LoginError($message, Flux_LoginError::UNEXPECTED);
}
return true;
}
/**
* Get account object for a particular user name.
*
* @param Flux_LoginAthenaGroup $loginAthenaGroup
* @param string $username
* @return mixed
* @access private
*/
private function getAccount(Flux_LoginAthenaGroup $loginAthenaGroup, $username)
{
$creditsTable = Flux::config('FluxTables.CreditsTable');
$creditColumns = 'credits.balance, credits.last_donation_date, credits.last_donation_amount';
$sql = "SELECT login.*, {$creditColumns} FROM {$loginAthenaGroup->loginDatabase}.login ";
$sql .= "LEFT OUTER JOIN {$loginAthenaGroup->loginDatabase}.{$creditsTable} AS credits ON login.account_id = credits.account_id ";
$sql .= "WHERE login.sex != 'S' AND login.group_id >= 0 AND login.userid = ? LIMIT 1";
$smt = $loginAthenaGroup->connection->getStatement($sql);
$res = $smt->execute(array($username));
if ($res && ($row = $smt->fetch())) {
return $row;
}
else {
return false;
}
}
/**
* Get available server names.
*
* @access public
*/
public function getAthenaServerNames()
{
if ($this->loginAthenaGroup) {
$names = array();
foreach ($this->loginAthenaGroup->athenaServers as $server) {
$names[] = $server->serverName;
}
return $names;
}
else {
return array();
}
}
/**
* Get a Flux_Athena instance by its name based on current server settings.
*
* @param string $name
* @access public
*/
public function getAthenaServer($name = null)
{
if (is_null($name) && $this->athenaServerName) {
return $this->getAthenaServer($this->athenaServerName);
}
if ($this->loginAthenaGroup && ($server = Flux::getAthenaServerByName($this->serverName, $name))) {
return $server;
}
else {
return false;
}
}
/**
* Get flash message.
*
* @return string
* @access public
*/
public function getMessage()
{
$message = $this->message;
$this->setMessageData(null);
return $message;
}
}
?>
================================================
FILE: lib/Flux/Template.php
================================================
params = $config->get('params');
$this->basePath = $config->get('basePath');
$this->modulePath = $config->get('modulePath');
$this->moduleName = $config->get('moduleName');
$this->themePath = $config->get('themePath');
$this->themeName = $config->get('themeName');
$this->actionName = $config->get('actionName');
$this->viewName = $config->get('viewName');
$this->headerName = $config->get('headerName');
$this->footerName = $config->get('footerName');
$this->useCleanUrls = $config->get('useCleanUrls');
$this->missingActionModuleAction = $config->get('missingActionModuleAction', false);
$this->missingViewModuleAction = $config->get('missingViewModuleAction', false);
$this->referer = isset($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : '';
// Read manifest file if exists
if (file_exists($this->themePath.'/'.$this->themeName.'/manifest.php')) {
$manifest = include($this->themePath.'/'.$this->themeName.'/manifest.php');
// Inherit views and controllers from another template
if (!empty($manifest['inherit'])) {
if (in_array($manifest['inherit'], self::$themeLoaded)) {
throw new Flux_Error('Circular dependencies in themes : ' . implode(' -> ', self::$themeLoaded) . ' -> ' . $manifest['inherit']);
}
$config->set('themeName', $manifest['inherit']);
self::$themeLoaded[] = $manifest['inherit'];
$this->parentTemplate = new Flux_Template($config);
}
}
}
/**
* Any data that gets set here will be available to all templates as global
* variables unless they are overridden by variables of the same name set
* in the render() method.
*
* @return array
* @access public
*/
public function setDefaultData(array &$data)
{
$this->defaultData = $data;
return $data;
}
/**
* Render a template, but before doing so, call the action file and render
* the header->view->footer in that order.
*
* @param array $dataArr Key=>value pairs of variables to be exposed to the template as globals.
* @access public
*/
public function render(array $dataArr = array())
{
// GZip compression.
if (Flux::config('GzipCompressOutput')) {
header('Accept-Encoding: gzip');
ini_set('zlib.output_handler', '');
ini_set('zlib.output_compression', 'On');
ini_set('zlib.output_compression_level', (int)Flux::config('GzipCompressionLevel'));
}
$addon = false;
$this->actionPath = sprintf('%s/%s/%s.php', $this->modulePath, $this->moduleName, $this->actionName);
if (!file_exists($this->actionPath)) {
foreach (Flux::$addons as $_tmpAddon) {
if ($_tmpAddon->respondsTo($this->moduleName, $this->actionName)) {
$addon = $_tmpAddon;
$this->actionPath = sprintf('%s/%s/%s.php', $addon->moduleDir, $this->moduleName, $this->actionName);
}
}
if (!$addon) {
$this->moduleName = $this->missingActionModuleAction[0];
$this->actionName = $this->missingActionModuleAction[1];
$this->viewName = $this->missingActionModuleAction[1];
$this->actionPath = sprintf('%s/%s/%s.php', $this->modulePath, $this->moduleName, $this->actionName);
}
}
$this->viewPath = $this->themePath(sprintf('%s/%s.php', $this->moduleName, $this->actionName), true);
if (!file_exists($this->viewPath) && $addon) {
$this->viewPath = $addon->getView( $this, $this->moduleName, $this->actionName);
if ( $this->viewPath === false ) {
$this->moduleName = $this->missingViewModuleAction[0];
$this->actionName = $this->missingViewModuleAction[1];
$this->viewName = $this->missingViewModuleAction[1];
$this->actionPath = sprintf('%s/%s/%s.php', $this->modulePath, $this->moduleName, $this->actionName);
$this->viewPath = $this->themePath(sprintf('%s/%s.php', $this->moduleName, $this->viewName), true);
}
}
$this->headerPath = $this->themePath($this->headerName.'.php', true);
$this->footerPath = $this->themePath($this->footerName.'.php', true);
$this->url = $this->url($this->moduleName, $this->actionName);
$this->urlWithQS = $this->url;
if (!empty($_SERVER['QUERY_STRING'])) {
if ($this->useCleanUrls) {
$this->urlWithQS .= "?{$_SERVER['QUERY_STRING']}";
}
else {
foreach (explode('&', trim($_SERVER['QUERY_STRING'], '&')) as $line) {
list ($key,$val) = explode('=', $line, 2);
$key = urldecode($key);
$val = urldecode($val);
if ($key != 'module' && $key != 'action') {
$this->urlWithQS .= sprintf('&%s=%s', urlencode($key), urlencode($val));
}
}
}
}
// Compatibility.
$this->urlWithQs = $this->urlWithQS;
// Tidy up!
if (Flux::config('OutputCleanHTML')) {
$dispatcher = Flux_Dispatcher::getInstance();
$tidyIgnore = false;
if (($tidyIgnores = Flux::config('TidyIgnore')) instanceOf Flux_Config) {
foreach ($tidyIgnores->getChildrenConfigs() as $ignore) {
$ignore = $ignore->toArray();
if (is_array($ignore) && array_key_exists('module', $ignore)) {
$module = $ignore['module'];
$action = array_key_exists('action', $ignore) ? $ignore['action'] : $dispatcher->defaultAction;
if ($this->moduleName == $module && $this->actionName == $action) {
$tidyIgnore = true;
}
}
}
}
if (!$tidyIgnore) {
ob_start();
}
}
// Merge with default data.
$data = array_merge($this->defaultData, $dataArr);
// Extract data array and make them appear as though they were global
// variables from the template.
extract($data, EXTR_REFS);
// Files object.
$files = new Flux_Config($_FILES);
$preprocessorPath = sprintf('%s/main/preprocess.php', $this->modulePath);
if (file_exists($preprocessorPath)) {
include $preprocessorPath;
}
include $this->actionPath;
$pageMenuFile = FLUX_ROOT."/modules/{$this->moduleName}/pagemenu/{$this->actionName}.php";
$pageMenuItems = array();
// Get the main menu file first (located in the actual module).
if (file_exists($pageMenuFile)) {
ob_start();
$pageMenuItems = include $pageMenuFile;
ob_end_clean();
}
$addonPageMenuFiles = glob(FLUX_ADDON_DIR."/*/modules/{$this->moduleName}/pagemenu/{$this->actionName}.php");
if ($addonPageMenuFiles) {
foreach ($addonPageMenuFiles as $addonPageMenuFile) {
ob_start();
$pageMenuItems = array_merge($pageMenuItems, include $addonPageMenuFile);
ob_end_clean();
}
}
if (file_exists($this->headerPath)) {
include $this->headerPath;
}
include $this->viewPath;
if (file_exists($this->footerPath)) {
include $this->footerPath;
}
// Really, tidy up!
if (Flux::config('OutputCleanHTML') && !$tidyIgnore && function_exists('tidy_repair_string')) {
$content = ob_get_clean();
$content = tidy_repair_string($content, array('indent' => true, 'wrap' => false, 'output-xhtml' => true), 'utf8');
echo $content;
}
}
/**
* Returns an array of menu items that should be diplayed from the theme.
* Only menu items the current user (and their group level) have access to
* will be returned as part of the array;
*
* @return array
*/
public function getMenuItems($adminMenus = false)
{
$auth = Flux_Authorization::getInstance();
$adminMenuLevel = Flux::config('AdminMenuGroupLevel');
$defaultAction = Flux_Dispatcher::getInstance()->defaultAction;
$menuItems = Flux::config('MenuItems');
$allowedItems = array();
if (!($menuItems instanceOf Flux_Config)) {
return array();
}
foreach ($menuItems->toArray() as $categoryName => $menu) {
foreach ($menu as $menuName => $menuItem) {
$module = array_key_exists('module', $menuItem) ? $menuItem['module'] : false;
$action = array_key_exists('action', $menuItem) ? $menuItem['action'] : $defaultAction;
$param = array_key_exists('param', $menuItem) ? $menuItem['param'] : array();
$exturl = array_key_exists('exturl', $menuItem) ? $menuItem['exturl'] : null;
if ($adminMenus) {
if ($auth->actionAllowed($module, $action) && $auth->config("modules.$module.$action") >= $adminMenuLevel) {
$allowedItems[] = array(
'name' => $menuName,
'exturl' => null,
'module' => $module,
'action' => $action,
'url' => $this->url($module, $action, $param)
);
}
}
else {
if (empty($allowedItems[$categoryName])) {
$allowedItems[$categoryName] = array();
}
if ($exturl) {
$allowedItems[$categoryName][] = array(
'name' => $menuName,
'exturl' => $exturl,
'module' => null,
'action' => null,
'url' => $exturl
);
}
elseif ($auth->actionAllowed($module, $action) && $auth->config("modules.$module.$action") < $adminMenuLevel) {
$allowedItems[$categoryName][] = array(
'name' => $menuName,
'exturl' => null,
'module' => $module,
'action' => $action,
'url' => $this->url($module, $action, $param)
);
}
}
}
}
return $allowedItems;
}
/**
* @see Flux_Template::getMenuItems()
*/
public function getAdminMenuItems()
{
return $this->getMenuItems(true);
}
/**
* Get sub-menu items for a particular module.
*
* @param string $moduleName
* @return array
*/
public function getSubMenuItems($moduleName = null)
{
$auth = Flux_Authorization::getInstance();
$moduleName = $moduleName ? $moduleName : $this->moduleName;
$subMenuItems = Flux::config('SubMenuItems');
$allowedItems = array();
if (!($subMenuItems instanceOf Flux_Config) || !( ($menus = $subMenuItems->get($moduleName)) instanceOf Flux_Config )) {
return array();
}
foreach ($menus->toArray() as $actionName => $menuName) {
if ($auth->actionAllowed($moduleName, $actionName)) {
$allowedItems[] = array('name' => $menuName, 'module' => $moduleName, 'action' => $actionName);
}
}
return $allowedItems;
}
/**
* Get an array of login server names.
*
* @return array
*/
public function getServerNames()
{
return array_keys(Flux::$loginAthenaGroupRegistry);
}
/**
* Determine if more than 1 server exists.
*
* @return bool
*/
public function hasManyServers()
{
return count(Flux::$loginAthenaGroupRegistry) > 1;
}
/**
* Obtain the absolute web path of the specified user path. Specify the
* path as a relative path.
*
* @param string $path Relative path from basePath.
* @param boolean $included
* @access public
*/
public function path($path, $included = false)
{
if (is_array($path)) {
$path = implode('/', $path);
}
if ($included === false) {
$path = "{$this->basePath}/$path";
}
return preg_replace('&/{2,}&', '/', $path);
}
/**
* Similar to the path() method, but uses the $themePath as the path from
* which the user-specified path is relative.
*
* @param string $path Relative path from themePath.
* @access public
*/
public function themePath($path, $included = false)
{
if (is_array($path)) {
$path = implode('/', $path);
}
// Remove frag for file checking.
$frag = "";
preg_match("/(\?|\#).*/", $path, $matches);
if (count($matches)) {
$frag = $matches[0];
$path = substr($path, 0, -strlen($frag));
}
$uri = $this->path("{$this->themePath}/{$this->themeName}/{$path}", $included);
// normalized basePath.
$base = preg_replace('/(\/+)$/', '', $this->basePath ) . '/';
$base = preg_quote( $base, '/' );
$chk = FLUX_ROOT .'/'. preg_replace('/^('.$base.')/', '', $uri );
// If file not found, search in parent's template.
if (!file_exists($chk) && !empty($this->parentTemplate)) {
$path = $this->parentTemplate->themePath($path, $included);
$chk = FLUX_ROOT .'/'. preg_replace('/^('.$base.')/', '', $path );
if (file_exists($chk)) {
$uri = $path;
}
} elseif (!file_exists($chk)) {
foreach (Flux::$addons as $_tmpAddon_key => $_tmpAddon) {
$chk = FLUX_ROOT .'/'. FLUX_ADDON_DIR .'/'. $_tmpAddon_key .'/'. preg_replace('/^('.$base.')/', '', $uri );
if (file_exists($chk)) {
$path = sprintf('%s/%s/%s', FLUX_ADDON_DIR, $_tmpAddon_key, preg_replace('/^('.$base.')/', '', $uri ));
$uri = $path;
break;
}
}
}
return $uri . $frag;
}
/**
* Create a URI based on the setting of $useCleanUrls. This will determine
* whether or not we will create a mod_rewrite-based clean URL or just a
* regular query string based one.
*
* @param string $moduleName
* @param string $actionName
* @access public
*/
public function url($moduleName, $actionName = null, $params = array())
{
$defaultAction = Flux_Dispatcher::getInstance()->defaultAction;
$serverProtocol = '';
$serverAddress = '';
if ($params instanceOf Flux_Config) {
$params = $params->toArray();
}
if (array_key_exists('_host', $params)) {
$_host = $params['_host'];
$_https = false;
if ($_host && ($addr=Flux::config('ServerAddress'))) {
if (array_key_exists('_https', $params)) {
$_https = $params['_https'];
}
elseif (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== "off") {
$_https = true;
}
else {
$_https = false;
}
$serverProtocol = $_https ? 'https://' : 'http://';
$serverAddress = $addr;
}
unset($params['_host']);
if (array_key_exists('_https', $params)) {
unset($params['_https']);
}
}
$queryString = '';
if (count($params)) {
$queryString .= Flux::config('UseCleanUrls') ? '?' : '&';
foreach ($params as $param => $value) {
$queryString .= sprintf('%s=%s&', $param, urlencode($value ?: ''));
}
$queryString = rtrim($queryString, '&');
}
if ($this->useCleanUrls) {
if ($actionName && $actionName != $defaultAction) {
$url = sprintf('%s/%s/%s/%s', $this->basePath, $moduleName, $actionName, $queryString);
}
else {
$url = sprintf('%s/%s/%s', $this->basePath, $moduleName, $queryString);
}
}
else {
if ($actionName && $actionName != $defaultAction) {
$url = sprintf('%s/?module=%s&action=%s%s', $this->basePath, $moduleName, $actionName, $queryString);
}
else {
$url = sprintf('%s/?module=%s%s', $this->basePath, $moduleName, $queryString);
}
}
return $serverProtocol.preg_replace('&/{2,}&', '/', "$serverAddress/$url");
}
/**
* Format currency strings.
*
* @param float $number Amount
* @return string Formatted amount
*/
public function formatCurrency($number)
{
$number = (float)$number;
$amount = number_format(
$number,
Flux::config('MoneyDecimalPlaces'),
Flux::config('MoneyDecimalSymbol'),
Flux::config('MoneyThousandsSymbol')
);
return $amount;
}
/**
* Format a MySQL DATE column according to the DateFormat config.
*
* @param string $data
* @return string
* @access public
*/
public function formatDate($date = null)
{
$ts = $date ? strtotime($date) : time();
return date(Flux::config('DateFormat'), $ts);
}
/**
* Format a MySQL DATETIME column according to the DateTimeFormat config.
*
* @param string $dataTime
* @return string
* @access public
*/
public function formatDateTime($dateTime = null)
{
$ts = $dateTime ? strtotime($dateTime) : time();
return date(Flux::config('DateTimeFormat'), $ts);
}
/**
* Create a series of select fields matching a MySQL DATE format.
*
* @param string $name
* @param string $value DATE formatted string.
* @param int $fowardYears
* @param int $backwardYears
* @return string
*/
public function dateField($name, $value = null, $fowardYears = null, $backwardYears = null)
{
if(!isset($fowardYears)) {
$fowardYears = (int)Flux::config('ForwardYears');
}
if(!isset($backwardYears)) {
$backwardYears = (int)Flux::config('BackwardYears');
}
$ts = $value && !preg_match('/^0000-00-00(?: 00:00:00)?$/', $value) ? strtotime($value) : time();
$year = ($year =$this->params->get("{$name}_year")) ? $year : date('Y', $ts);
$month = ($month=$this->params->get("{$name}_month")) ? $month : date('m', $ts);
$day = ($day =$this->params->get("{$name}_day")) ? $day : date('d', $ts);
$fw = $year + $fowardYears;
$bw = $year - $backwardYears;
// Get years.
$years = sprintf('';
// Get months.
$months = sprintf('';
// Get days.
$days = sprintf('';
return sprintf('%s-%s-%s', $years, $months, $days);
}
/**
* Create a series of select fields matching a MySQL DATETIME format.
*
* @param string $name
* @param string $value DATETIME formatted string.
* @return string
*/
public function dateTimeField($name, $value = null)
{
$dateField = $this->dateField($name, $value);
$ts = $value ? strtotime($value) : strtotime('00:00:00');
$hour = date('H', $ts);
$minute = date('i', $ts);
$second = date('s', $ts);
// Get hours.
$hours = sprintf('';
// Get minutes.
$minutes = sprintf('';
// Get seconds.
$seconds = sprintf('';
return sprintf('%s @ %s:%s:%s', $dateField, $hours, $minutes, $seconds);
}
/**
* Returns "up" or "down" in a span HTML element with either the class
* .up or .down, based on the value of $bool. True returns up, false
* returns down.
*
* @param bool $bool True/false value
* @return string Up/down
*/
public function serverUpDown($bool)
{
$class = $bool ? 'up' : 'down';
return sprintf('%s', $class, $bool ? 'Online' : 'Offline');
}
/**
* Redirect client to another location. Script execution is terminated
* after instructing the client to redirect.
*
* @param string $location
*/
public function redirect($location = null)
{
if (is_null($location)) {
$location = $this->basePath;
}
header("Location: $location");
exit;
}
/**
* Guess the HTTP server's current full URL.
*
* @param bool $withRequest True to include REQUEST_URI, false if not.
* @return string URL
*/
public function entireUrl($withRequest = true)
{
$proto = empty($_SERVER['HTTPS']) || $_SERVER['HTTPS'] === "off" ? 'http://' : 'https://';
$hostname = empty($_SERVER['HTTP_HOST']) ? $_SERVER['SERVER_NAME'] : $_SERVER['HTTP_HOST'];
$request = $_SERVER['REQUEST_URI'];
if ($withRequest) {
$url = $proto.$hostname.$request;
}
else {
$url = $proto.$hostname.'/'.$this->basePath;
}
$url = rtrim(preg_replace('&/{2,}&', '/', $url), '/');
return $url;
}
/**
* Convenience method for retrieving a paginator instance.
*
* @param int $total Total number of records.
* @param array $options Paginator options.
* @return Flux_Paginator
* @access public
*/
public function getPaginator($total, array $options = array())
{
$paginator = new Flux_Paginator($total, $this->url($this->moduleName, $this->actionName, array('_host' => false)), $options);
return $paginator;
}
/**
* Link to an account view page.
*
* @param int $accountID
* @param string $text
* @return mixed
* @access public
*/
public function linkToAccount($accountID, $text)
{
if ($accountID) {
$url = $this->url('account', 'view', array('id' => $accountID));
return sprintf('%s', $url, htmlspecialchars($text));
}
else {
return false;
}
}
/**
* Link to an account search.
*
* @param array $params
* @param string $text
* @return mixed
* @access public
*/
public function linkToAccountSearch($params, $text)
{
if (is_array($params) && count($params)) {
$url = $this->url('account', 'index', $params);
return sprintf('%s', $url, htmlspecialchars($text));
}
else {
return false;
}
}
/**
* Link to a character view page.
*
* @param int $charID
* @param string $text
* @return mixed
* @access public
*/
public function linkToCharacter($charID, $text, $server = null)
{
if ($charID) {
$params = array('id' => $charID);
if ($server) {
$params['preferred_server'] = $server;
}
$url = $this->url('character', 'view', $params);
return sprintf('%s', $url, htmlspecialchars($text));
}
else {
return false;
}
}
/**
* Deny entry to a page if called. This method should be used from a module
* script, and no where else.
*/
public function deny()
{
$location = $this->url('unauthorized');
$this->redirect($location);
}
/**
* Get the full gender string from a gender letter (e.g. M for Male).
*
* @param string $gender
* @return string
* @access public
*/
public function genderText($gender)
{
switch (strtoupper($gender)) {
case 'M':
return Flux::message('GenderTypeMale');
break;
case 'F':
return Flux::message('GenderTypeFemale');
break;
case 'S':
return Flux::message('GenderTypeServer');
break;
default:
return false;
break;
}
}
/**
* Get the account state name corresponding to the state number.
*
* @param int $state
* @return mixed State name or false.
* @access public
*/
public function accountStateText($state)
{
$text = false;
$state = (int)$state;
switch ($state) {
case 0:
$text = Flux::message('AccountStateNormal');
$class = 'state-normal';
break;
case 5:
$text = Flux::message('AccountStatePermBanned');
$class = 'state-permanently-banned';
break;
}
if ($text) {
$text = htmlspecialchars($text);
return sprintf('%s', $class, $text);
}
else {
return false;
}
}
/**
* Get the job class name from a job ID.
*
* @param int $id
* @return mixed Job class or false.
* @access public
*/
public function jobClassText($id)
{
return Flux::getJobClass($id);
}
/**
* Return hidden input fields containing module and action names based on
* the setting of UseCleanUrls.
*
* @param string $moduleName
* @param string $actionName
* @return string
* @access public
*/
public function moduleActionFormInputs($moduleName, $actionName = null)
{
$inputs = '';
if (!Flux::config('UseCleanUrls')) {
if (!$actionName) {
$dispatcher = Flux_Dispatcher::getInstance();
$actionName = $dispatcher->defaultAction;
}
$inputs .= sprintf('', htmlspecialchars($moduleName))."\n";
$inputs .= sprintf('', htmlspecialchars($actionName));
}
return $inputs;
}
/**
* Get the homun class name from a class ID.
*
* @param int $id
* @return mixed Job class or false.
* @access public
*/
public function homunClassText($id)
{
return Flux::getHomunClass($id);
}
/**
* Get the item type name from an item type.
*
* @return Item type or false.
* @access public
*/
public function itemTypeText($id)
{
return Flux::getItemType($id);
}
public function itemSubTypeText($id1, $id2)
{
if($id1 == 'Weapon' || $id1 == 'Ammo' || $id1 == 'Card')
return Flux::getItemSubType(strtolower($id1), strtolower($id2));
else
return false;
}
public function itemRandOption($id, $value)
{
return sprintf(Flux::getRandOption($id), $value);
}
/**
* Get the item information from splitting a delimiter
* Used for renewal ATK and MATK as well as equip_level_min and equip_level_max.
*
* @param PDOStatement $object
* @param string $field
* @param string $delimiter
* @param array $inputs
* @return PDOStatement $object
* @access public
*/
public function itemFieldExplode($object, $field, $delimiter, $inputs)
{
$fields = explode($delimiter, $object->$field);
foreach($inputs as $i => $input) {
$object->$input = isset($fields[$i]) ? $fields[$i] : NULL;
}
return $object;
}
/**
* Get the equip location combination name from an equip location combination.
*
* @param int $id
* @return mixed Equip location combination or false.
* @access public
*/
public function equipLocationCombinationText($id)
{
return Flux::getEquipLocationCombination($id);
}
/**
*
*
*/
public function emblem($guildID, $serverName = null, $athenaServerName = null)
{
if (!$serverName) {
$serverName = Flux::$sessionData->loginAthenaGroup->serverName;
}
if (!$athenaServerName) {
$athenaServerName = Flux::$sessionData->getAthenaServer(Flux::$sessionData->athenaServerName);
}
return $this->url('guild', 'emblem',
array('login' => $serverName, 'charmap' => $athenaServerName, 'id' => $guildID));
}
/**
* Redirect to login page if the user is not currently logged in.
*/
public function loginRequired($message = null)
{
$dispatcher = Flux_Dispatcher::getInstance();
$dispatcher->loginRequired($this->basePath, $message);
}
/**
* Link to a item view page.
*
* @param int $itemID
* @param string $text
* @return mixed
* @access public
*/
public function linkToItem($itemID, $text, $server = null)
{
if ($itemID) {
$params = array('id' => $itemID);
if ($server) {
$params['preferred_server'] = $server;
}
$url = $this->url('item', 'view', $params);
return sprintf('%s', $url, htmlspecialchars($text));
}
else {
return false;
}
}
/**
*
*/
public function displayScript($scriptText)
{
$lines = !empty($scriptText) ? preg_split('/\s+|<|>|\[|\]/', $scriptText, -1, PREG_SPLIT_NO_EMPTY) : [];
$text = '';
$script = array();
foreach ($lines as $num => $line) {
$text .= "$line\n";
$lineNum = sprintf('%d', $num + 1);
$lineCode = sprintf('%s', htmlspecialchars($line));
$script[] = sprintf('
%s %s
', $lineNum, $lineCode);
}
return trim($text) == '' ? '' : implode("\n", $script);
}
/**
*
*/
public function banTypeText($banType)
{
$banType = (int)$banType;
if (!$banType) {
return Flux::message('BanTypeUnbanned');
}
elseif ($banType === 2) {
return Flux::message('BanTypePermBanned');
}
elseif ($banType === 1) {
return Flux::message('BanTypeTempBanned');
}
else {
return Flux::message('UnknownLabel');
}
}
/**
*
*/
public function equippableJobs($equipJob)
{
$jobs = array();
$equipJobs = Flux::getEquipJobsList();
foreach ($equipJob as $name) {
$jobs[] = $equipJobs[$name];
if($name == 'job_all') break;
}
return $jobs;
}
/**
*
*/
public function GetJobsList($isRenewal)
{
$jobs = Flux::getEquipJobsList($isRenewal);
return $jobs;
}
/**
*
*/
public function GetClassList($isRenewal)
{
$jobs = Flux::getEquipUpperList($isRenewal);
return $jobs;
}
/**
*
*/
public function tradeRestrictions($list)
{
$restrictions = array();
$Restrictions = Flux::getTradeRestrictionList();
foreach ($list as $name) {
$restrictions[] = $Restrictions[$name];
}
return $restrictions;
}
/**
*
*/
public function itemsFlags($list)
{
$flags = array();
$Flags = Flux::getItemFlagList();
foreach ($list as $name) {
$flags[] = $Flags[$name];
}
return $flags;
}
/**
* Link to a monster view page.
*
* @param int $monsterID
* @param string $text
* @return mixed
* @access public
*/
public function linkToMonster($monsterID, $text, $server = null)
{
if ($monsterID) {
$params = array('id' => $monsterID);
if ($server) {
$params['preferred_server'] = $server;
}
$url = $this->url('monster', 'view', $params);
return sprintf('%s', $url, htmlspecialchars($text));
}
else {
return false;
}
}
/**
*
*/
public function equipLocations($equipLoc)
{
$locations = array();
asort($equipLoc);
if(count($equipLoc) > 1) {
$equipLocs = Flux::getEquipLocationCombination();
$equipLoc = array(htmlspecialchars(implode('/', $equipLoc)));
} else {
$equipLocs = Flux::getEquipLocationList();
}
foreach ($equipLoc as $key => $name) {
$locations[] = $equipLocs[$name];
}
if(is_array($equipLoc))
return htmlspecialchars(implode(' / ', $locations));
else
return false;
}
/**
*
*/
public function equipUpper($equipUpper, $isRenewal = 1)
{
$upper = array();
$table = Flux::getEquipUpperList($isRenewal);
foreach ($equipUpper as $name) {
$upper[] = $table[$name];
if($name == 'class_all') break;
}
return $upper;
}
/**
* Link to a guild view page.
*
* @param int $guildID
* @param string $text
* @return mixed
* @access public
*/
public function linkToGuild($guild_id, $text, $server = null)
{
if ($guild_id) {
$params = array('id' => $guild_id);
if ($server) {
$params['preferred_server'] = $server;
}
$url = $this->url('guild', 'view', $params);
return sprintf('%s', $url, htmlspecialchars($text));
}
else {
return false;
}
}
/**
*
*/
public function donateButton($amount)
{
ob_start();
include FLUX_DATA_DIR.'/paypal/button.php';
$button = ob_get_clean();
return $button;
}
/**
*
*/
public function shopItemImage($shopItemID, $serverName = null, $athenaServerName = null)
{
if (!$serverName) {
$serverName = Flux::$sessionData->loginAthenaGroup->serverName;
}
if (!$athenaServerName) {
$athenaServerName = Flux::$sessionData->getAthenaServer(Flux::$sessionData->athenaServerName);
}
if (!$serverName || !$athenaServerName) {
return false;
}
$dir = FLUX_DATA_DIR."/itemshop/$serverName/$athenaServerName";
$exts = implode('|', array_map('preg_quote', Flux::config('ShopImageExtensions')->toArray()));
$imgs = glob("$dir/$shopItemID.*");
if (is_array($imgs)) {
$files = preg_grep("/\.($exts)$/", $imgs);
}
else {
$files = array();
}
if (empty($files)) {
return false;
}
else {
reset($files);
$imageFile = current($files);
return preg_replace('&/{2,}&', '/', "{$this->basePath}/$imageFile");
}
}
/**
*
*/
public function iconImage($itemID)
{
$path = sprintf(FLUX_DATA_DIR."/items/icons/".Flux::config('ItemIconNameFormat'), $itemID);
$link = preg_replace('&/{2,}&', '/', "{$this->basePath}/$path");
if(Flux::config('DivinePrideIntegration') && !file_exists($path)) {
$download_link = "https://static.divine-pride.net/images/items/item/$itemID.png";
$data = get_headers($download_link, true);
$size = isset($data['Content-Length']) ? (int)$data['Content-Length'] : 0;
if($size != 0)
file_put_contents(sprintf(FLUX_DATA_DIR."/items/icons/".Flux::config('ItemIconNameFormat'), $itemID), file_get_contents($download_link));
}
return file_exists($path) ? $link : false;
}
/**
*
*/
public function itemImage($itemID)
{
$path = sprintf(FLUX_DATA_DIR."/items/images/".Flux::config('ItemImageNameFormat'), $itemID);
$link = preg_replace('&/{2,}&', '/', "{$this->basePath}/$path");
if(Flux::config('DivinePrideIntegration') && !file_exists($path)) {
$download_link = "https://static.divine-pride.net/images/items/collection/$itemID.png";
$data = get_headers($download_link, true);
$size = isset($data['Content-Length']) ? (int)$data['Content-Length'] : 0;
if($size != 0)
file_put_contents(sprintf(FLUX_DATA_DIR."/items/images/".Flux::config('ItemImageNameFormat'), $itemID), file_get_contents($download_link));
}
return file_exists($path) ? $link : false;
}
/**
*
*/
public function monsterImage($monsterID)
{
$path = sprintf(FLUX_DATA_DIR."/monsters/".Flux::config('MonsterImageNameFormat'), $monsterID);
$link = preg_replace('&/{2,}&', '/', "{$this->basePath}/$path");
if(Flux::config('DivinePrideIntegration') && !file_exists($path)) {
$download_link = "https://static.divine-pride.net/images/mobs/png/$monsterID.png";
$data = get_headers($download_link, true);
$size = isset($data['Content-Length']) ? (int)$data['Content-Length'] : 0;
if($size != 0)
file_put_contents(sprintf(FLUX_DATA_DIR."/monsters/".Flux::config('MonsterImageNameFormat'), $monsterID), file_get_contents($download_link));
}
return file_exists($path) ? $link : false;
}
/**
*
*/
public function jobImage($gender, $jobID)
{
$path = sprintf(FLUX_DATA_DIR."/jobs/images/%s/".Flux::config('JobImageNameFormat'), $gender, $jobID);
$link = preg_replace('&/{2,}&', '/', "{$this->basePath}/$path");
return file_exists($path) ? $link : false;
}
/**
*
*/
public function monsterMode($modes, $ai)
{
$monsterModes = Flux::config('MonsterModes')->toArray();
$monsterAI = Flux::config('MonsterAI')->toArray();
$array = array();
if($ai)
foreach ($monsterAI[$ai] as $mode) {
if(isset($monsterModes[$mode]))
$array[] = $monsterModes[$mode];
}
if($modes)
foreach ($modes as $mode) {
if(isset($monsterModes[$mode]))
$array[] = $monsterModes[$mode];
}
return array_unique($array);
}
/**
* Return the template name ("default")
* @access public
*/
public function getName()
{
return $this->themeName;
}
/**
* Caps values to min/max
* @access public
*/
public function cap_value($amount, $min, $max)
{
return ($amount >= $max) ? $max : (($amount <= $min) ? $min : $amount);
}
}
?>
================================================
FILE: lib/Flux/TemporaryTable.php
================================================
connection = $connection;
$this->tableName = $tableName;
$this->fromTables = $fromTables;
if (empty($fromTables)) {
self::raise("One or more tables must be specified to import into the temporary table '$tableName'");
}
// Find the first table.
reset($this->fromTables);
$firstTable = $this->fromTables[0];
$secondTable = $this->fromTables[1];
if ($this->create($secondTable)) {
// Insert initial row set.
// Rows imported from the following tables should overwrite these rows.
if (!$this->import($firstTable, false)) {
self::raise("Failed to import rows from initial table '$firstTable'");
}
foreach (array_slice($this->fromTables, 1) as $table) {
if (!$this->import($table)) {
self::raise("Failed to import/replace rows from table '$table'");
}
}
}
}
/**
* Create actual temporary table in the database.
*
* @param string $firstTable
* @return bool
* @access private
*/
private function create($firstTable)
{
// Drop temporary table before hand.
$this->drop();
$sth = $this->connection->getStatement("DESCRIBE $firstTable");
$res = $sth->execute();
if (!$res) {
return false;
}
$cols = $sth->fetchAll();
$bind = array();
$sql = "CREATE TEMPORARY TABLE {$this->tableName} (";
$primary = false;
$uniques = array();
$indices = array();
// Origin column, indicates which table the record came from.
$varcharLength = $this->findVarcharLength();
$origin = new Flux_DataObject();
$origin->Field = 'origin_table';
$origin->Type = "varchar($varcharLength)";
$origin->Null = 'YES';
$origin->Key = '';
$origin->Default = null;
$origin->Extra = '';
// Add origin column.
$cols[] = $origin;
foreach ($cols as $col) {
// Determine default value.
if ($col->Default) {
$default = 'DEFAULT ?';
$bind[] = $col->Default;
}
else {
$default = '';
}
// Find primary key.
if ($col->Key == 'PRI') {
$primary = $col->Field;
}
// Find any unique keys.
elseif ($col->Key == 'UNI') {
$uniques[] = $col->Field;
}
// Find any indexed keys.
elseif ($col->Key == 'MUL') {
$indices[] = $col->Field;
}
$null = $col->Null == 'YES' ? 'NULL' : 'NOT NULL'; // Determine NULL status.
$sql .= rtrim("\n\t`{$col->Field}` {$col->Type} $null $default {$col->Extra},");
}
// Add primary key.
if ($primary) {
$sql .= "\n\tPRIMARY KEY( `$primary` ),";
}
// Add unique keys.
if ($uniques) {
foreach ($uniques as $unique) {
$sql .= "\n\tUNIQUE KEY `$unique` ( `$unique` ),";
}
}
// Add index keys.
if ($indices) {
foreach ($indices as $index) {
$sql .= "\n\tKEY `$index` (`$index`),";
}
}
$sql = rtrim($sql, ', ');
$sql .= "\n);";
$sth = $this->connection->getStatement($sql);
$res = $sth->execute($bind);
if (!$res) {
$message = "Failed to create temporary table '{$this->tableName}'.\n";
$message .= sprintf('Error info: %s', print_r($sth->errorInfo(), true));
self::raise($message);
}
return true;
}
/**
* Import rows from a specified table into the temporary table, optionally
* overwriting duplicate primay key rows.
*
* @param string $table
* @param bool $overwrite
* @return bool
* @access private
*/
private function import($table, $overwrite = true)
{
$act = $overwrite ? 'REPLACE' : 'INSERT';
$sql = "$act INTO $this->tableName SELECT $table.*, '$table' FROM $table";
$sth = $this->connection->getStatement($sql);
return $sth->execute();
}
/**
* Find the length of the longest table name, which should be used to
* determine the length of the VARCHAR field in the temporary table.
*
* @return int
* @access private
*/
private function findVarcharLength()
{
$length = 0;
foreach ($this->fromTables as $table) {
if (($strlen=strlen($table)) > $length) {
$length = $strlen;
}
}
return $length;
}
/**
* Throw an exception.
*
* @param string $message
* @throws Flux_Error
* @access private
* @static
*/
private static function raise($message = '')
{
$class = self::$exceptionClass;
throw new $class($message);
}
/**
* Drop temporary table.
*
* @return bool
* @access public
*/
public function drop()
{
$sql = "DROP TEMPORARY TABLE IF EXISTS {$this->tableName}";
$sth = $this->connection->getStatement($sql);
return $sth->execute();
}
public function __destruct()
{
$this->drop();
}
}
?>
================================================
FILE: lib/Flux/index.html
================================================
================================================
FILE: lib/Flux.php
================================================
merge($importAppConfig, true, true);
}
// Server configuration files are not merged, instead they replace the original.
if (array_key_exists('serversConfigFileImport', $options) && file_exists($options['serversConfigFileImport'])) {
$importServersConfig = self::parseServersConfigFile($options['serversConfigFileImport'], true);
self::$serversConfig = $importServersConfig;
}
// Using newer language system.
self::$messagesConfig = self::parseLanguageConfigFile();
// Initialize server objects.
self::initializeServerObjects();
// Initialize add-ons.
self::initializeAddons();
}
/**
* Initialize each Login/Char/Map server object and contain them in their
* own collective Athena object.
*
* This is also part of the Flux initialization phase.
*
* @access public
*/
public static function initializeServerObjects()
{
foreach (self::$serversConfig->getChildrenConfigs() as $key => $config) {
$connection = new Flux_Connection($config->getDbConfig(), $config->getLogsDbConfig(), $config->getWebDbConfig());
$loginServer = new Flux_LoginServer($config->getLoginServer());
// LoginAthenaGroup maintains the grouping of a central login
// server and its underlying Athena objects.
self::$servers[$key] = new Flux_LoginAthenaGroup($config->getServerName(), $connection, $loginServer);
// Add into registry.
self::registerServerGroup($config->getServerName(), self::$servers[$key]);
foreach ($config->getCharMapServers()->getChildrenConfigs() as $charMapServer) {
$charServer = new Flux_CharServer($charMapServer->getCharServer());
$mapServer = new Flux_MapServer($charMapServer->getMapServer());
// Create the collective server object, Flux_Athena.
$athena = new Flux_Athena($charMapServer, $loginServer, $charServer, $mapServer);
self::$servers[$key]->addAthenaServer($athena);
// Add into registry.
self::registerAthenaServer($config->getServerName(), $charMapServer->getServerName(), $athena);
}
}
}
/**
*
*/
public static function initializeAddons()
{
if (!is_dir(FLUX_ADDON_DIR)) {
return false;
}
foreach (glob(FLUX_ADDON_DIR.'/*') as $addonDir) {
if (is_dir($addonDir)) {
$addonName = basename($addonDir);
$addonObject = new Flux_Addon($addonName, $addonDir);
self::$addons[$addonName] = $addonObject;
// Merge configurations.
self::$appConfig->merge($addonObject->addonConfig);
self::$messagesConfig->merge($addonObject->messagesConfig, false);
}
}
}
/**
* Wrapper method for setting and getting values from the appConfig.
*
* @param string $key
* @param mixed $value
* @param array $options
* @access public
*/
public static function config($key, $value = null, $options = array())
{
if (!is_null($value)) {
return self::$appConfig->set($key, $value, $options);
}
else {
return self::$appConfig->get($key);
}
}
/**
* Wrapper method for setting and getting values from the messagesConfig.
*
* @param string $key
* @param mixed $value
* @param array $options
* @access public
*/
public static function message($key, $value = null, $options = array())
{
if (!is_null($value)) {
return self::$messagesConfig->set($key, $value, $options);
}
if (!is_null($tmp=self::$messagesConfig->get($key)))
return $tmp;
else
return ' '.$key;
}
/**
* Convenience method for raising Flux_Error exceptions.
*
* @param string $message Message to pass to constructor.
* @throws Flux_Error
* @access public
*/
public static function raise($message)
{
throw new Flux_Error($message);
}
/**
* Parse PHP array into Flux_Config instance.
*
* @param array $configArr
* @access public
*/
public static function parseConfig(array $configArr)
{
return new Flux_Config($configArr);
}
/**
* Parse a PHP array returned as the result of an included file into a
* Flux_Config configuration object.
*
* @param string $filename
* @access public
*/
public static function parseConfigFile($filename, $cache=true)
{
$basename = basename(str_replace(' ', '', ucwords(str_replace(array('/', '\\', '_'), ' ', $filename))), '.php').'.cache.php';
$cachefile = FLUX_DATA_DIR."/tmp/$basename";
$directory = FLUX_DATA_DIR.'/tmp';
if (!is_dir($directory))
mkdir($directory, 0600);
if ($cache && file_exists($cachefile) && filemtime($cachefile) > filemtime($filename)) {
return unserialize(file_get_contents($cachefile, false, null, 28));
}
else {
ob_start();
// Uses require, thus assumes the file returns an array.
$config = require $filename;
ob_end_clean();
// Cache config file.
$cf = self::parseConfig($config);
if ($cache) {
$fp = fopen($cachefile, 'w');
if ( !$fp ){
self::raise("Failed to write ".$cachefile." permission error or data/tmp not exist in Flux::parseConfigFile()");
}
fwrite($fp, '');
fwrite($fp, $s=serialize($cf), strlen($s));
fclose($fp);
}
return $cf;
}
}
/**
* Parse a file in an application-config specific manner.
*
* @param string $filename
* @param bool $import Whether this is an import config or not
* @access public
*/
public static function parseAppConfigFile($filename, $import = false)
{
$config = self::parseConfigFile($filename, false);
if (!$config->getServerAddress() && !$import) {
self::raise("ServerAddress must be specified in your application config.");
}
$themes = $config->get('ThemeName', false);
if ((!$themes || count($themes) < 1) && !$import) {
self::raise('ThemeName is required in application configuration.');
}
if ($themes) {
foreach ($themes as $themeName) {
if (!self::themeExists($themeName)) {
self::raise("The selected theme '$themeName' does not exist.");
}
}
}
if (!($config->getPayPalReceiverEmails() instanceof Flux_Config)
&& !($import && $config->getPayPalReceiverEmails() === null)) {
self::raise("PayPalReceiverEmails must be an array.");
}
// Sanitize BaseURI. (leading forward slash is mandatory.)
$baseURI = $config->get('BaseURI');
if (!is_null($baseURI)) {
if (strlen($baseURI) && $baseURI[0] != '/') {
$config->set('BaseURI', "/$baseURI");
}
elseif (trim($baseURI) === '') {
$config->set('BaseURI', '/');
}
}
return $config;
}
/**
* Parse a file in a servers-config specific manner. This method gets a bit
* nasty so beware of ugly code ;)
*
* @param string $filename
* @param bool $import Whether this is an import config or not
* @access public
*/
public static function parseServersConfigFile($filename, $import = false)
{
$config = self::parseConfigFile($filename);
$options = array('overwrite' => false, 'force' => true); // Config::set() options.
$serverNames = array();
$athenaServerNames = array();
if (!count($config->toArray()) && !$import) {
self::raise('At least one server configuration must be present.');
}
foreach ($config->getChildrenConfigs() as $topConfig) {
//
// Top-level normalization.
//
if (!($serverName = $topConfig->getServerName())) {
self::raise('ServerName is required for each top-level server configuration, check your servers configuration file.');
}
elseif (in_array($serverName, $serverNames)) {
self::raise("The server name '$serverName' has already been configured. Please use another name.");
}
$serverNames[] = $serverName;
$athenaServerNames[$serverName] = array();
$topConfig->setDbConfig(array(), $options);
$topConfig->setLogsDbConfig(array(), $options);
$topConfig->setWebDbConfig(array(), $options);
$topConfig->setLoginServer(array(), $options);
$topConfig->setCharMapServers(array(), $options);
$dbConfig = $topConfig->getDbConfig();
$logsDbConfig = $topConfig->getLogsDbConfig();
$webDbConfig = $topConfig->getWebDbConfig();
$loginServer = $topConfig->getLoginServer();
foreach (array($dbConfig, $logsDbConfig, $webDbConfig) as $_dbConfig) {
$_dbConfig->setHostname('localhost', $options);
$_dbConfig->setUsername('ragnarok', $options);
$_dbConfig->setPassword('ragnarok', $options);
$_dbConfig->setPersistent(true, $options);
}
$loginServer->setDatabase($dbConfig->getDatabase(), $options);
$loginServer->setUseMD5(true, $options);
// Raise error if missing essential configuration directives.
if (!$loginServer->getAddress()) {
self::raise('Address is required for each LoginServer section in your servers configuration.');
}
elseif (!$loginServer->getPort()) {
self::raise('Port is required for each LoginServer section in your servers configuration.');
}
if (!$topConfig->getCharMapServers() || !count($topConfig->getCharMapServers()->toArray())) {
self::raise('CharMapServers must be an array and contain at least 1 char/map server entry.');
}
foreach ($topConfig->getCharMapServers()->getChildrenConfigs() as $charMapServer) {
//
// Char/Map normalization.
//
$expRates = array(
'Base' => 100,
'Job' => 100,
'Mvp' => 100
);
$dropRates = array(
'DropRateCap' => 9000,
'Common' => 100,
'CommonBoss' => 100,
'CommonMVP' => 100,
'CommonMin' => 1,
'CommonMax' => 10000,
'Heal' => 100,
'HealBoss' => 100,
'HealMVP' => 100,
'HealMin' => 1,
'HealMax' => 10000,
'Useable' => 100,
'UseableBoss' => 100,
'UseableMVP' => 100,
'UseableMin' => 1,
'UseableMax' => 10000,
'Equip' => 100,
'EquipBoss' => 100,
'EquipMVP' => 100,
'EquipMin' => 1,
'EquipMax' => 10000,
'Card' => 100,
'CardBoss' => 100,
'CardMVP' => 100,
'CardMin' => 1,
'CardMax' => 10000,
'MvpItem' => 100,
'MvpItemMin' => 1,
'MvpItemMax' => 10000,
'MvpItemMode' => 0
);
$charMapServer->setExpRates($expRates, $options);
$charMapServer->setDropRates($dropRates, $options);
$charMapServer->setRenewal(true, $options);
$charMapServer->setCharServer(array(), $options);
$charMapServer->setMapServer(array(), $options);
$charMapServer->setDatabase($dbConfig->getDatabase(), $options);
if (!($athenaServerName = $charMapServer->getServerName())) {
self::raise('ServerName is required for each CharMapServers pair in your servers configuration.');
}
elseif (in_array($athenaServerName, $athenaServerNames[$serverName])) {
self::raise("The server name '$athenaServerName' under '$serverName' has already been configured. Please use another name.");
}
$athenaServerNames[$serverName][] = $athenaServerName;
$charServer = $charMapServer->getCharServer();
if (!$charServer->getAddress()) {
self::raise('Address is required for each CharServer section in your servers configuration.');
}
elseif (!$charServer->getPort()) {
self::raise('Port is required for each CharServer section in your servers configuration.');
}
$mapServer = $charMapServer->getMapServer();
if (!$mapServer->getAddress()) {
self::raise('Address is required for each MapServer section in your servers configuration.');
}
elseif (!$mapServer->getPort()) {
self::raise('Port is required for each MapServer section in your servers configuration.');
}
}
}
return $config;
}
/**
* Parses a messages configuration file. (Deprecated)
*
* @param string $filename
* @access public
*/
public static function parseMessagesConfigFile($filename)
{
$config = self::parseConfigFile($filename);
// Nothing yet.
return $config;
}
/**
* Parses a language configuration file, can also parse a language config
* for any addon.
*
* @param string $addonName
* @access public
*/
public static function parseLanguageConfigFile($addonName=null)
{
$default = $addonName ? FLUX_ADDON_DIR."/$addonName/lang/en_us.php" : FLUX_LANG_DIR.'/en_us.php';
$current = $default;
if ($lang=self::config('DefaultLanguage')) {
$current = $addonName ? FLUX_ADDON_DIR."/$addonName/lang/$lang.php" : FLUX_LANG_DIR."/$lang.php";
}
$languages = self::getAvailableLanguages();
if(!empty($_COOKIE["language"]) && array_key_exists($_COOKIE["language"], $languages))
{
$lang = $_COOKIE["language"];
$current = $addonName ? FLUX_ADDON_DIR."/$addonName/lang/$lang.php" : FLUX_LANG_DIR."/$lang.php";
}
if (file_exists($default)) {
$def = self::parseConfigFile($default);
}
else {
$tmp = array();
$def = new Flux_Config($tmp);
}
if ($current != $default && file_exists($current)) {
$cur = self::parseConfigFile($current);
$def->merge($cur, false);
}
return $def;
}
/**
* Check whether or not a theme exists.
*
* @return bool
* @access public
*/
public static function themeExists($themeName)
{
return is_dir(FLUX_THEME_DIR."/$themeName");
}
/**
* Register the server group into the registry.
*
* @param string $serverName Server group's name.
* @param Flux_LoginAthenaGroup Server group object.
* @return Flux_LoginAthenaGroup
* @access private
*/
private static function registerServerGroup($serverName, Flux_LoginAthenaGroup $serverGroup)
{
self::$loginAthenaGroupRegistry[$serverName] = $serverGroup;
return $serverGroup;
}
/**
* Register the Athena server into the registry.
*
* @param string $serverName Server group's name.
* @param string $athenaServerName Athena server's name.
* @param Flux_Athena $athenaServer Athena server object.
* @return Flux_Athena
* @access private
*/
private static function registerAthenaServer($serverName, $athenaServerName, Flux_Athena $athenaServer)
{
if (!array_key_exists($serverName, self::$athenaServerRegistry) || !is_array(self::$athenaServerRegistry[$serverName])) {
self::$athenaServerRegistry[$serverName] = array();
}
self::$athenaServerRegistry[$serverName][$athenaServerName] = $athenaServer;
return $athenaServer;
}
/**
* Get Flux_LoginAthenaGroup server object by its ServerName.
*
* @param string $serverName Server group name.
* @return mixed Returns Flux_LoginAthenaGroup instance or false on failure.
* @access public
*/
public static function getServerGroupByName($serverName)
{
$registry = &self::$loginAthenaGroupRegistry;
if (array_key_exists($serverName, $registry) && $registry[$serverName] instanceOf Flux_LoginAthenaGroup) {
return $registry[$serverName];
}
else {
return false;
}
}
/**
* Get Flux_Athena instance by its group/server names.
*
* @param string $serverName Server group name.
* @param string $athenaServerName Athena server name.
* @return mixed Returns Flux_Athena instance or false on failure.
* @access public
*/
public static function getAthenaServerByName($serverName, $athenaServerName)
{
$registry = &self::$athenaServerRegistry;
if (array_key_exists($serverName, $registry) && array_key_exists($athenaServerName, $registry[$serverName]) &&
$registry[$serverName][$athenaServerName] instanceOf Flux_Athena) {
return $registry[$serverName][$athenaServerName];
}
else {
return false;
}
}
/**
* Hashes a password for use in comparison with the login.user_pass column.
*
* @param string $password Plain text password.
* @return string Returns hashed password.
* @access public
*/
public static function hashPassword($password)
{
// Default hashing schema is MD5.
return md5($password);
}
/**
* Get the job class name from a job ID.
*
* @param int $id
* @return mixed Job class or false.
* @access public
*/
public static function getJobClass($id)
{
$key = "JobClasses.$id";
$class = self::config($key);
if ($class) {
return $class;
}
else {
return false;
}
}
/**
* Get the job ID from a job class name.
*
* @param string $class
* @return mixed Job ID or false.
* @access public
*/
public static function getJobID($class)
{
$index = self::config('JobClassIndex')->toArray();
if (array_key_exists($class, $index)) {
return $index[$class];
}
else {
return false;
}
}
/**
* Get the homunculus class name from a homun class ID.
*
* @param int $id
* @return mixed Class name or false.
* @access public
*/
public static function getHomunClass($id)
{
$key = "HomunClasses.$id";
$class = self::config($key);
if ($class) {
return $class;
}
else {
return false;
}
}
/**
* Get the item type name from an item type.
*
* @return Item Type or false.
* @access public
*/
public static function getItemType($id1)
{
if (is_null($id1))
return false;
$type = self::config("ItemTypes")->toArray();
if ($type[strtolower($id1)] != NULL) {
return $type[strtolower($id1)];
}
else {
return false;
}
}
public static function getItemSubType($id1, $id2)
{
$subtype = "ItemSubTypes.$id1.$id2";
$result = self::config($subtype);
if ($result) {
return $result;
}
else {
return false;
}
}
/**
* return random option description.
*/
public static function getRandOption($id1)
{
$key = "RandomOptions.$id1";
$option = self::config($key);
if ($option) {
return $option;
}
else {
return false;
}
}
/**
* Get the equip location combination name from an equip location combination type.
*
* @param int $id
* @return mixed Equip Location Combination or false.
* @access public
*/
public static function getEquipLocationCombination()
{
$equiplocations = Flux::config('EquipLocationCombinations')->toArray();
return $equiplocations;
}
/**
* Process donations that have been put on hold.
*/
public static function processHeldCredits()
{
$txnLogTable = self::config('FluxTables.TransactionTable');
$trustTable = self::config('FluxTables.DonationTrustTable');
$loginAthenaGroups = self::$loginAthenaGroupRegistry;
list ($cancel, $accept) = array(array(), array());
foreach ($loginAthenaGroups as $loginAthenaGroup) {
$sql = "SELECT account_id, payer_email, credits, mc_gross, txn_id, hold_until ";
$sql .= "FROM {$loginAthenaGroup->loginDatabase}.$txnLogTable ";
$sql .= "WHERE account_id > 0 AND hold_until IS NOT NULL AND payment_status = 'Completed'";
$sth = $loginAthenaGroup->connection->getStatement($sql);
if ($sth->execute() && ($txn=$sth->fetchAll())) {
foreach ($txn as $t) {
$sql = "SELECT id FROM {$loginAthenaGroup->loginDatabase}.$txnLogTable ";
$sql .= "WHERE payment_status IN ('Cancelled_Reversed', 'Reversed', 'Refunded') AND parent_txn_id = ? LIMIT 1";
$sth = $loginAthenaGroup->connection->getStatement($sql);
if ($sth->execute(array($t->txn_id)) && ($r=$sth->fetch()) && $r->id) {
$cancel[] = $t->txn_id;
}
elseif (strtotime($t->hold_until) <= time()) {
$accept[] = $t;
}
}
}
if (!empty($cancel)) {
$ids = implode(', ', array_fill(0, count($cancel), '?'));
$sql = "UPDATE {$loginAthenaGroup->loginDatabase}.$txnLogTable ";
$sql .= "SET credits = 0, hold_until = NULL WHERE txn_id IN ($ids)";
$sth = $loginAthenaGroup->connection->getStatement($sql);
$sth->execute($cancel);
}
$sql2 = "INSERT INTO {$loginAthenaGroup->loginDatabase}.$trustTable (account_id, email, create_date)";
$sql2 .= "VALUES (?, ?, NOW())";
$sth2 = $loginAthenaGroup->connection->getStatement($sql2);
$sql3 = "SELECT id FROM {$loginAthenaGroup->loginDatabase}.$trustTable WHERE ";
$sql3 .= "delete_date IS NULL AND account_id = ? AND email = ? LIMIT 1";
$sth3 = $loginAthenaGroup->connection->getStatement($sql3);
$idvals = array();
foreach ($accept as $txn) {
$loginAthenaGroup->loginServer->depositCredits($txn->account_id, $txn->credits, $txn->mc_gross);
$sth3->execute(array($txn->account_id, $txn->payer_email));
$row = $sth3->fetch();
if (!$row) {
$sth2->execute(array($txn->account_id, $txn->payer_email));
}
$idvals[] = $txn->txn_id;
}
if (!empty($idvals)) {
$ids = implode(', ', array_fill(0, count($idvals), '?'));
$sql = "UPDATE {$loginAthenaGroup->loginDatabase}.$txnLogTable ";
$sql .= "SET hold_until = NULL WHERE txn_id IN ($ids)";
$sth = $loginAthenaGroup->connection->getStatement($sql);
$sth->execute($idvals);
}
}
}
/**
*
*/
public static function pruneUnconfirmedAccounts()
{
$tbl = Flux::config('FluxTables.AccountCreateTable');
foreach (self::$loginAthenaGroupRegistry as $loginAthenaGroup) {
$db = $loginAthenaGroup->loginDatabase;
$sql = "DELETE $db.login, $db.$tbl FROM $db.login INNER JOIN $db.$tbl ";
$sql .= "WHERE login.account_id = $tbl.account_id AND $tbl.confirmed = 0 ";
$sql .= "AND $tbl.confirm_code IS NOT NULL AND $tbl.confirm_expire <= NOW()";
$sth = $loginAthenaGroup->connection->getStatement($sql);
$sth->execute();
}
}
/**
* Get array of equip_location bits. (bit => loc_name pairs)
* @return array
*/
public static function getEquipLocationList()
{
$equiplocations = Flux::config('EquipLocations')->toArray();
return $equiplocations;
}
/**
* Get array of equip_upper bits. (bit => upper_name pairs)
* @return array
*/
public static function getEquipUpperList($isRenewal = 1)
{
$equipupper = Flux::config('EquipUpper.0')->toArray();
if($isRenewal)
$equipupper = array_merge($equipupper, Flux::config('EquipUpper.1')->toArray());
return $equipupper;
}
/**
* Get array of equip_jobs bits. (bit => job_name pairs)
*/
public static function getEquipJobsList($isRenewal = 1)
{
$equipjobs = Flux::config('EquipJobs.0')->toArray();
if($isRenewal)
$equipjobs = array_merge($equipjobs, Flux::config('EquipJobs.1')->toArray());
return $equipjobs;
}
/**
* Get array of trade restrictions
*/
public static function getTradeRestrictionList()
{
$restrictions = Flux::config('TradeRestriction')->toArray();
return $restrictions;
}
/**
* Get array of item flags
*/
public static function getItemFlagList()
{
$flags = Flux::config('ItemFlags')->toArray();
return $flags;
}
/**
* Check whether a particular item type is stackable.
* @param int $type
* @return bool
*/
public static function isStackableItemType($type)
{
$nonstackables = array(1, 4, 5, 7, 8, 9);
return !in_array($type, $nonstackables);
}
/**
* Perform a bitwise AND from each bit in getEquipUpperList() on $bitmask
* to determine which bits have been set.
* @param int $bitmask
* @return array
*/
public static function equipUpperToArray($bitmask, $isRenewal = 1)
{
$arr = array();
$bits = self::getEquipUpperList($isRenewal);
foreach ($bits as $bit => $name) {
if ($bitmask & $bit) {
$arr[] = $bit;
}
}
return $arr;
}
/**
* Perform a bitwise AND from each bit in getEquipJobsList() on $bitmask
* to determine which bits have been set.
* @param int $bitmask
* @return array
*/
public static function equipJobsToArray($bitmask)
{
$arr = array();
$bits = self::getEquipJobsList();
foreach ($bits as $bit => $name) {
if ($bitmask & $bit) {
$arr[] = $bit;
}
}
return $arr;
}
/**
*
*/
public static function monsterModeToArray($bitmask)
{
$arr = array();
$bits = self::config('MonsterModes')->toArray();
foreach ($bits as $name) {
$arr[] = $name;
}
return $arr;
}
/**
*
*/
public static function elementName($ele)
{
$element = Flux::config("Elements")->toArray();
return is_null($element[$ele]) ? $element['Neutral'] : $element[$ele];
}
/**
*
*/
public static function monsterRaceName($race)
{
$races = Flux::config("MonsterRaces")->toArray();
return is_null($races[$race]) ? $races['Formless'] : $races[$race];
}
/**
*
*/
public static function monsterSizeName($size)
{
$sizes = Flux::config("MonsterSizes")->toArray();
return is_null($sizes[$size]) ? $sizes['Small'] : $sizes[$size];
}
public static function getAvailableLanguages()
{
$langs_available = array_diff(scandir(FLUX_LANG_DIR), array('..', '.'));
$dictionary = [];
foreach($langs_available as $lang_file) {
$lang_key = str_replace('.php', '', $lang_file);
$lang_conf = self::parseConfigFile(FLUX_LANG_DIR.'/'.$lang_file);
$lang_name = $lang_conf->get('Language');
$dictionary[$lang_key] = $lang_name;
}
return $dictionary;
}
}
?>
================================================
FILE: lib/functions/discordwebhook.php
================================================
$message);
$curl = curl_init($url);
curl_setopt($curl, CURLOPT_HTTPHEADER, array('Content-type: application/json'));
curl_setopt($curl, CURLOPT_CUSTOMREQUEST, "POST");
curl_setopt($curl, CURLOPT_POSTFIELDS, json_encode($data));
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
return curl_exec($curl);
}
?>
================================================
FILE: lib/functions/getReposVersion.php
================================================
================================================
FILE: lib/functions/imagecreatefrombmpstring.php
================================================
*/
function imagecreatefrombmpstring($im) {
$header = unpack("vtype/Vsize/v2reserved/Voffset", substr($im, 0, 14));
$info = unpack("Vsize/Vwidth/Vheight/vplanes/vbits/Vcompression/Vimagesize/Vxres/Vyres/Vncolor/Vimportant", substr($im, 14, 40));
extract($info);
extract($header);
if($type != 0x4D42)
return false;
$palette_size = $offset - 54;
$imres=imagecreatetruecolor($width, $height);
imagealphablending($imres, false);
imagesavealpha($imres, true);
$pal=array();
if($palette_size) {
$palette = substr($im, 54, $palette_size);
$j = 0; $n = 0;
while($j < $palette_size) {
$b = ord($palette[$j++]);
$g = ord($palette[$j++]);
$r = ord($palette[$j++]);
$a = ord($palette[$j++]);
if ($a > 127)
$a = 127; // alpha = 255 on 0xFF00FF
if ( ($r & 0xf8 == 0xf8) && ($g == 0) && ($b & 0xf8 == 0xf8))
$a = 127; // alpha = 255 on 0xFF00FF
$pal[$n++] = imagecolorallocatealpha($imres, $r, $g, $b, $a);
}
}
$scan_line_size = (($bits * $width) + 7) >> 3;
$scan_line_align = ($scan_line_size & 0x03) ? 4 - ($scan_line_size & 0x03): 0;
for($i = 0, $l = $height - 1; $i < $height; $i++, $l--) {
$scan_line = substr($im, $offset + (($scan_line_size + $scan_line_align) * $l), $scan_line_size);
if($bits == 24) {
$j = 0; $n = 0;
while($j < $scan_line_size) {
$b = ord($scan_line[$j++]);
$g = ord($scan_line[$j++]);
$r = ord($scan_line[$j++]);
$a = 0;
if ( ($r & 0xf8 == 0xf8) && ($g == 0) && ($b & 0xf8 == 0xf8))
$a = 127; // alpha = 255 on 0xFF00FF
$col=imagecolorallocatealpha($imres, $r, $g, $b, $a);
imagesetpixel($imres, $n++, $i, $col);
}
}
else if($bits == 8) {
$j = 0;
while($j < $scan_line_size) {
$col = $pal[ord($scan_line[$j++])];
imagesetpixel($imres, $j-1, $i, $col);
}
}
else if($bits == 4) {
$j = 0; $n = 0;
while($j < $scan_line_size) {
$byte = ord($scan_line[$j++]);
$p1 = $byte >> 4;
$p2 = $byte & 0x0F;
imagesetpixel($imres, $n++, $i, $pal[$p1]);
imagesetpixel($imres, $n++, $i, $pal[$p2]);
}
}
else if($bits == 1) {
$j = 0; $n = 0;
while($j < $scan_line_size) {
$byte = ord($scan_line[$j++]);
$p1 = (int) (($byte & 0x80) != 0);
$p2 = (int) (($byte & 0x40) != 0);
$p3 = (int) (($byte & 0x20) != 0);
$p4 = (int) (($byte & 0x10) != 0);
$p5 = (int) (($byte & 0x08) != 0);
$p6 = (int) (($byte & 0x04) != 0);
$p7 = (int) (($byte & 0x02) != 0);
$p8 = (int) (($byte & 0x01) != 0);
imagesetpixel($imres, $n++, $i, $pal[$p1]);
imagesetpixel($imres, $n++, $i, $pal[$p2]);
imagesetpixel($imres, $n++, $i, $pal[$p3]);
imagesetpixel($imres, $n++, $i, $pal[$p4]);
imagesetpixel($imres, $n++, $i, $pal[$p5]);
imagesetpixel($imres, $n++, $i, $pal[$p6]);
imagesetpixel($imres, $n++, $i, $pal[$p7]);
imagesetpixel($imres, $n++, $i, $pal[$p8]);
}
}
}
return $imres;
}
?>
================================================
FILE: lib/functions/index.html
================================================
================================================
FILE: lib/index.html
================================================
================================================
FILE: lib/phpmailer/LICENSE
================================================
GNU LESSER GENERAL PUBLIC LICENSE
Version 2.1, February 1999
Copyright (C) 1991, 1999 Free Software Foundation, Inc.
59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
[This is the first released version of the Lesser GPL. It also counts
as the successor of the GNU Library Public License, version 2, hence
the version number 2.1.]
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
Licenses are intended to guarantee your freedom to share and change
free software--to make sure the software is free for all its users.
This license, the Lesser General Public License, applies to some
specially designated software packages--typically libraries--of the
Free Software Foundation and other authors who decide to use it. You
can use it too, but we suggest you first think carefully about whether
this license or the ordinary General Public License is the better
strategy to use in any particular case, based on the explanations below.
When we speak of free software, we are referring to freedom of use,
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 this service if you wish); that you receive source code or can get
it if you want it; that you can change the software and use pieces of
it in new free programs; and that you are informed that you can do
these things.
To protect your rights, we need to make restrictions that forbid
distributors to deny you these rights or to ask you to surrender these
rights. These restrictions translate to certain responsibilities for
you if you distribute copies of the library or if you modify it.
For example, if you distribute copies of the library, whether gratis
or for a fee, you must give the recipients all the rights that we gave
you. You must make sure that they, too, receive or can get the source
code. If you link other code with the library, you must provide
complete object files to the recipients, so that they can relink them
with the library after making changes to the library and recompiling
it. And you must show them these terms so they know their rights.
We protect your rights with a two-step method: (1) we copyright the
library, and (2) we offer you this license, which gives you legal
permission to copy, distribute and/or modify the library.
To protect each distributor, we want to make it very clear that
there is no warranty for the free library. Also, if the library is
modified by someone else and passed on, the recipients should know
that what they have is not the original version, so that the original
author's reputation will not be affected by problems that might be
introduced by others.
Finally, software patents pose a constant threat to the existence of
any free program. We wish to make sure that a company cannot
effectively restrict the users of a free program by obtaining a
restrictive license from a patent holder. Therefore, we insist that
any patent license obtained for a version of the library must be
consistent with the full freedom of use specified in this license.
Most GNU software, including some libraries, is covered by the
ordinary GNU General Public License. This license, the GNU Lesser
General Public License, applies to certain designated libraries, and
is quite different from the ordinary General Public License. We use
this license for certain libraries in order to permit linking those
libraries into non-free programs.
When a program is linked with a library, whether statically or using
a shared library, the combination of the two is legally speaking a
combined work, a derivative of the original library. The ordinary
General Public License therefore permits such linking only if the
entire combination fits its criteria of freedom. The Lesser General
Public License permits more lax criteria for linking other code with
the library.
We call this license the "Lesser" General Public License because it
does Less to protect the user's freedom than the ordinary General
Public License. It also provides other free software developers Less
of an advantage over competing non-free programs. These disadvantages
are the reason we use the ordinary General Public License for many
libraries. However, the Lesser license provides advantages in certain
special circumstances.
For example, on rare occasions, there may be a special need to
encourage the widest possible use of a certain library, so that it becomes
a de-facto standard. To achieve this, non-free programs must be
allowed to use the library. A more frequent case is that a free
library does the same job as widely used non-free libraries. In this
case, there is little to gain by limiting the free library to free
software only, so we use the Lesser General Public License.
In other cases, permission to use a particular library in non-free
programs enables a greater number of people to use a large body of
free software. For example, permission to use the GNU C Library in
non-free programs enables many more people to use the whole GNU
operating system, as well as its variant, the GNU/Linux operating
system.
Although the Lesser General Public License is Less protective of the
users' freedom, it does ensure that the user of a program that is
linked with the Library has the freedom and the wherewithal to run
that program using a modified version of the Library.
The precise terms and conditions for copying, distribution and
modification follow. Pay close attention to the difference between a
"work based on the library" and a "work that uses the library". The
former contains code derived from the library, whereas the latter must
be combined with the library in order to run.
GNU LESSER GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License Agreement applies to any software library or other
program which contains a notice placed by the copyright holder or
other authorized party saying it may be distributed under the terms of
this Lesser General Public License (also called "this License").
Each licensee is addressed as "you".
A "library" means a collection of software functions and/or data
prepared so as to be conveniently linked with application programs
(which use some of those functions and data) to form executables.
The "Library", below, refers to any such software library or work
which has been distributed under these terms. A "work based on the
Library" means either the Library or any derivative work under
copyright law: that is to say, a work containing the Library or a
portion of it, either verbatim or with modifications and/or translated
straightforwardly into another language. (Hereinafter, translation is
included without limitation in the term "modification".)
"Source code" for a work means the preferred form of the work for
making modifications to it. For a library, complete source code means
all the source code for all modules it contains, plus any associated
interface definition files, plus the scripts used to control compilation
and installation of the library.
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running a program using the Library is not restricted, and output from
such a program is covered only if its contents constitute a work based
on the Library (independent of the use of the Library in a tool for
writing it). Whether that is true depends on what the Library does
and what the program that uses the Library does.
1. You may copy and distribute verbatim copies of the Library's
complete source code as you receive it, in any medium, provided that
you conspicuously and appropriately publish on each copy an
appropriate copyright notice and disclaimer of warranty; keep intact
all the notices that refer to this License and to the absence of any
warranty; and distribute a copy of this License along with the
Library.
You may charge a fee for the physical act of transferring a copy,
and you may at your option offer warranty protection in exchange for a
fee.
2. You may modify your copy or copies of the Library or any portion
of it, thus forming a work based on the Library, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) The modified work must itself be a software library.
b) You must cause the files modified to carry prominent notices
stating that you changed the files and the date of any change.
c) You must cause the whole of the work to be licensed at no
charge to all third parties under the terms of this License.
d) If a facility in the modified Library refers to a function or a
table of data to be supplied by an application program that uses
the facility, other than as an argument passed when the facility
is invoked, then you must make a good faith effort to ensure that,
in the event an application does not supply such function or
table, the facility still operates, and performs whatever part of
its purpose remains meaningful.
(For example, a function in a library to compute square roots has
a purpose that is entirely well-defined independent of the
application. Therefore, Subsection 2d requires that any
application-supplied function or table used by this function must
be optional: if the application does not supply it, the square
root function must still compute square roots.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Library,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Library, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote
it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Library.
In addition, mere aggregation of another work not based on the Library
with the Library (or with a work based on the Library) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may opt to apply the terms of the ordinary GNU General Public
License instead of this License to a given copy of the Library. To do
this, you must alter all the notices that refer to this License, so
that they refer to the ordinary GNU General Public License, version 2,
instead of to this License. (If a newer version than version 2 of the
ordinary GNU General Public License has appeared, then you can specify
that version instead if you wish.) Do not make any other change in
these notices.
Once this change is made in a given copy, it is irreversible for
that copy, so the ordinary GNU General Public License applies to all
subsequent copies and derivative works made from that copy.
This option is useful when you wish to copy part of the code of
the Library into a program that is not a library.
4. You may copy and distribute the Library (or a portion or
derivative of it, under Section 2) in object code or executable form
under the terms of Sections 1 and 2 above provided that you accompany
it with the complete corresponding machine-readable source code, which
must be distributed under the terms of Sections 1 and 2 above on a
medium customarily used for software interchange.
If distribution of object code is made by offering access to copy
from a designated place, then offering equivalent access to copy the
source code from the same place satisfies the requirement to
distribute the source code, even though third parties are not
compelled to copy the source along with the object code.
5. A program that contains no derivative of any portion of the
Library, but is designed to work with the Library by being compiled or
linked with it, is called a "work that uses the Library". Such a
work, in isolation, is not a derivative work of the Library, and
therefore falls outside the scope of this License.
However, linking a "work that uses the Library" with the Library
creates an executable that is a derivative of the Library (because it
contains portions of the Library), rather than a "work that uses the
library". The executable is therefore covered by this License.
Section 6 states terms for distribution of such executables.
When a "work that uses the Library" uses material from a header file
that is part of the Library, the object code for the work may be a
derivative work of the Library even though the source code is not.
Whether this is true is especially significant if the work can be
linked without the Library, or if the work is itself a library. The
threshold for this to be true is not precisely defined by law.
If such an object file uses only numerical parameters, data
structure layouts and accessors, and small macros and small inline
functions (ten lines or less in length), then the use of the object
file is unrestricted, regardless of whether it is legally a derivative
work. (Executables containing this object code plus portions of the
Library will still fall under Section 6.)
Otherwise, if the work is a derivative of the Library, you may
distribute the object code for the work under the terms of Section 6.
Any executables containing that work also fall under Section 6,
whether or not they are linked directly with the Library itself.
6. As an exception to the Sections above, you may also combine or
link a "work that uses the Library" with the Library to produce a
work containing portions of the Library, and distribute that work
under terms of your choice, provided that the terms permit
modification of the work for the customer's own use and reverse
engineering for debugging such modifications.
You must give prominent notice with each copy of the work that the
Library is used in it and that the Library and its use are covered by
this License. You must supply a copy of this License. If the work
during execution displays copyright notices, you must include the
copyright notice for the Library among them, as well as a reference
directing the user to the copy of this License. Also, you must do one
of these things:
a) Accompany the work with the complete corresponding
machine-readable source code for the Library including whatever
changes were used in the work (which must be distributed under
Sections 1 and 2 above); and, if the work is an executable linked
with the Library, with the complete machine-readable "work that
uses the Library", as object code and/or source code, so that the
user can modify the Library and then relink to produce a modified
executable containing the modified Library. (It is understood
that the user who changes the contents of definitions files in the
Library will not necessarily be able to recompile the application
to use the modified definitions.)
b) Use a suitable shared library mechanism for linking with the
Library. A suitable mechanism is one that (1) uses at run time a
copy of the library already present on the user's computer system,
rather than copying library functions into the executable, and (2)
will operate properly with a modified version of the library, if
the user installs one, as long as the modified version is
interface-compatible with the version that the work was made with.
c) Accompany the work with a written offer, valid for at
least three years, to give the same user the materials
specified in Subsection 6a, above, for a charge no more
than the cost of performing this distribution.
d) If distribution of the work is made by offering access to copy
from a designated place, offer equivalent access to copy the above
specified materials from the same place.
e) verify that the user has already received a copy of these
materials or that you have already sent this user a copy.
For an executable, the required form of the "work that uses the
Library" must include any data and utility programs needed for
reproducing the executable from it. However, as a special exception,
the materials to be distributed need not include anything that is
normally distributed (in either source or binary form) with the major
components (compiler, kernel, and so on) of the operating system on
which the executable runs, unless that component itself accompanies
the executable.
It may happen that this requirement contradicts the license
restrictions of other proprietary libraries that do not normally
accompany the operating system. Such a contradiction means you cannot
use both them and the Library together in an executable that you
distribute.
7. You may place library facilities that are a work based on the
Library side-by-side in a single library together with other library
facilities not covered by this License, and distribute such a combined
library, provided that the separate distribution of the work based on
the Library and of the other library facilities is otherwise
permitted, and provided that you do these two things:
a) Accompany the combined library with a copy of the same work
based on the Library, uncombined with any other library
facilities. This must be distributed under the terms of the
Sections above.
b) Give prominent notice with the combined library of the fact
that part of it is a work based on the Library, and explaining
where to find the accompanying uncombined form of the same work.
8. You may not copy, modify, sublicense, link with, or distribute
the Library except as expressly provided under this License. Any
attempt otherwise to copy, modify, sublicense, link with, or
distribute the Library is void, and will automatically terminate your
rights under this License. However, parties who have received copies,
or rights, from you under this License will not have their licenses
terminated so long as such parties remain in full compliance.
9. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Library or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Library (or any work based on the
Library), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Library or works based on it.
10. Each time you redistribute the Library (or any work based on the
Library), the recipient automatically receives a license from the
original licensor to copy, distribute, link with or modify the Library
subject to these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties with
this License.
11. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
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
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Library at all. For example, if a patent
license would not permit royalty-free redistribution of the Library by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Library.
If any portion of this section is held invalid or unenforceable under any
particular circumstance, the balance of the section is intended to apply,
and the section as a whole is intended to apply in other circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
12. If the distribution and/or use of the Library is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Library under this License may add
an explicit geographical distribution limitation excluding those countries,
so that distribution is permitted only in or among countries not thus
excluded. In such case, this License incorporates the limitation as if
written in the body of this License.
13. The Free Software Foundation may publish revised and/or new
versions of the Lesser 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 Library
specifies a version number of this License which applies to it and
"any later version", you have the option of following the terms and
conditions either of that version or of any later version published by
the Free Software Foundation. If the Library does not specify a
license version number, you may choose any version ever published by
the Free Software Foundation.
14. If you wish to incorporate parts of the Library into other free
programs whose distribution conditions are incompatible with these,
write to the author to ask for permission. For software which is
copyrighted by the Free Software Foundation, write to the Free
Software Foundation; we sometimes make exceptions for this. Our
decision will be guided by the two goals of preserving the free status
of all derivatives of our free software and of promoting the sharing
and reuse of software generally.
NO WARRANTY
15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
OTHER PARTIES PROVIDE THE LIBRARY "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
LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
AND/OR REDISTRIBUTE THE LIBRARY 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
LIBRARY (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 LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Libraries
If you develop a new library, and you want it to be of the greatest
possible use to the public, we recommend making it free software that
everyone can redistribute and change. You can do so by permitting
redistribution under these terms (or, alternatively, under the terms of the
ordinary General Public License).
To apply these terms, attach the following notices to the library. It is
safest to attach them to the start of each source file to most effectively
convey 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 library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library 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
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
Also add information on how to contact you by electronic and paper mail.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the library, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the
library `Frob' (a library for tweaking knobs) written by James Random Hacker.
, 1 April 1990
Ty Coon, President of Vice
That's all there is to it!
================================================
FILE: lib/phpmailer/PHPMailerAutoload.php
================================================
* @author Jim Jagielski (jimjag)
* @author Andy Prevost (codeworxtech)
* @author Brent R. Matzelle (original founder)
* @copyright 2012 - 2014 Marcus Bointon
* @copyright 2010 - 2012 Jim Jagielski
* @copyright 2004 - 2009 Andy Prevost
* @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
* @note This program is distributed in the hope that it will be useful - WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE.
*/
/**
* PHPMailer SPL autoloader.
* @param string $classname The name of the class to load
*/
function PHPMailerAutoload($classname)
{
//Can't use __DIR__ as it's only in PHP 5.3+
$filename = dirname(__FILE__).DIRECTORY_SEPARATOR.'class.'.strtolower($classname).'.php';
if (is_readable($filename)) {
require $filename;
}
}
if (version_compare(PHP_VERSION, '5.1.2', '>=')) {
//SPL autoloading was introduced in PHP 5.1.2
if (version_compare(PHP_VERSION, '5.3.0', '>=')) {
spl_autoload_register('PHPMailerAutoload', true, true);
} else {
spl_autoload_register('PHPMailerAutoload');
}
// Removed fallback to older versions due to deprecated command [Everade]
}
================================================
FILE: lib/phpmailer/VERSION
================================================
5.2.26
================================================
FILE: lib/phpmailer/class.phpmailer.php
================================================
* @author Jim Jagielski (jimjag)
* @author Andy Prevost (codeworxtech)
* @author Brent R. Matzelle (original founder)
* @copyright 2012 - 2014 Marcus Bointon
* @copyright 2010 - 2012 Jim Jagielski
* @copyright 2004 - 2009 Andy Prevost
* @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
* @note This program is distributed in the hope that it will be useful - WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE.
*/
/**
* PHPMailer - PHP email creation and transport class.
* @package PHPMailer
* @author Marcus Bointon (Synchro/coolbru)
* @author Jim Jagielski (jimjag)
* @author Andy Prevost (codeworxtech)
* @author Brent R. Matzelle (original founder)
*/
class PHPMailer
{
/**
* The PHPMailer Version number.
* @var string
*/
public $Version = '5.2.26';
/**
* Email priority.
* Options: null (default), 1 = High, 3 = Normal, 5 = low.
* When null, the header is not set at all.
* @var integer
*/
public $Priority = null;
/**
* The character set of the message.
* @var string
*/
public $CharSet = 'iso-8859-1';
/**
* The MIME Content-type of the message.
* @var string
*/
public $ContentType = 'text/plain';
/**
* The message encoding.
* Options: "8bit", "7bit", "binary", "base64", and "quoted-printable".
* @var string
*/
public $Encoding = '8bit';
/**
* Holds the most recent mailer error message.
* @var string
*/
public $ErrorInfo = '';
/**
* The From email address for the message.
* @var string
*/
public $From = 'root@localhost';
/**
* The From name of the message.
* @var string
*/
public $FromName = 'Root User';
/**
* The Sender email (Return-Path) of the message.
* If not empty, will be sent via -f to sendmail or as 'MAIL FROM' in smtp mode.
* @var string
*/
public $Sender = '';
/**
* The Return-Path of the message.
* If empty, it will be set to either From or Sender.
* @var string
* @deprecated Email senders should never set a return-path header;
* it's the receiver's job (RFC5321 section 4.4), so this no longer does anything.
* @link https://tools.ietf.org/html/rfc5321#section-4.4 RFC5321 reference
*/
public $ReturnPath = '';
/**
* The Subject of the message.
* @var string
*/
public $Subject = '';
/**
* An HTML or plain text message body.
* If HTML then call isHTML(true).
* @var string
*/
public $Body = '';
/**
* The plain-text message body.
* This body can be read by mail clients that do not have HTML email
* capability such as mutt & Eudora.
* Clients that can read HTML will view the normal Body.
* @var string
*/
public $AltBody = '';
/**
* An iCal message part body.
* Only supported in simple alt or alt_inline message types
* To generate iCal events, use the bundled extras/EasyPeasyICS.php class or iCalcreator
* @link http://sprain.ch/blog/downloads/php-class-easypeasyics-create-ical-files-with-php/
* @link http://kigkonsult.se/iCalcreator/
* @var string
*/
public $Ical = '';
/**
* The complete compiled MIME message body.
* @access protected
* @var string
*/
protected $MIMEBody = '';
/**
* The complete compiled MIME message headers.
* @var string
* @access protected
*/
protected $MIMEHeader = '';
/**
* Extra headers that createHeader() doesn't fold in.
* @var string
* @access protected
*/
protected $mailHeader = '';
/**
* Word-wrap the message body to this number of chars.
* Set to 0 to not wrap. A useful value here is 78, for RFC2822 section 2.1.1 compliance.
* @var integer
*/
public $WordWrap = 0;
/**
* Which method to use to send mail.
* Options: "mail", "sendmail", or "smtp".
* @var string
*/
public $Mailer = 'mail';
/**
* The path to the sendmail program.
* @var string
*/
public $Sendmail = '/usr/sbin/sendmail';
/**
* Whether mail() uses a fully sendmail-compatible MTA.
* One which supports sendmail's "-oi -f" options.
* @var boolean
*/
public $UseSendmailOptions = true;
/**
* Path to PHPMailer plugins.
* Useful if the SMTP class is not in the PHP include path.
* @var string
* @deprecated Should not be needed now there is an autoloader.
*/
public $PluginDir = '';
/**
* The email address that a reading confirmation should be sent to, also known as read receipt.
* @var string
*/
public $ConfirmReadingTo = '';
/**
* The hostname to use in the Message-ID header and as default HELO string.
* If empty, PHPMailer attempts to find one with, in order,
* $_SERVER['SERVER_NAME'], gethostname(), php_uname('n'), or the value
* 'localhost.localdomain'.
* @var string
*/
public $Hostname = '';
/**
* An ID to be used in the Message-ID header.
* If empty, a unique id will be generated.
* You can set your own, but it must be in the format "",
* as defined in RFC5322 section 3.6.4 or it will be ignored.
* @see https://tools.ietf.org/html/rfc5322#section-3.6.4
* @var string
*/
public $MessageID = '';
/**
* The message Date to be used in the Date header.
* If empty, the current date will be added.
* @var string
*/
public $MessageDate = '';
/**
* SMTP hosts.
* Either a single hostname or multiple semicolon-delimited hostnames.
* You can also specify a different port
* for each host by using this format: [hostname:port]
* (e.g. "smtp1.example.com:25;smtp2.example.com").
* You can also specify encryption type, for example:
* (e.g. "tls://smtp1.example.com:587;ssl://smtp2.example.com:465").
* Hosts will be tried in order.
* @var string
*/
public $Host = 'localhost';
/**
* The default SMTP server port.
* @var integer
* @TODO Why is this needed when the SMTP class takes care of it?
*/
public $Port = 25;
/**
* The SMTP HELO of the message.
* Default is $Hostname. If $Hostname is empty, PHPMailer attempts to find
* one with the same method described above for $Hostname.
* @var string
* @see PHPMailer::$Hostname
*/
public $Helo = '';
/**
* What kind of encryption to use on the SMTP connection.
* Options: '', 'ssl' or 'tls'
* @var string
*/
public $SMTPSecure = '';
/**
* Whether to enable TLS encryption automatically if a server supports it,
* even if `SMTPSecure` is not set to 'tls'.
* Be aware that in PHP >= 5.6 this requires that the server's certificates are valid.
* @var boolean
*/
public $SMTPAutoTLS = true;
/**
* Whether to use SMTP authentication.
* Uses the Username and Password properties.
* @var boolean
* @see PHPMailer::$Username
* @see PHPMailer::$Password
*/
public $SMTPAuth = false;
/**
* Options array passed to stream_context_create when connecting via SMTP.
* @var array
*/
public $SMTPOptions = array();
/**
* SMTP username.
* @var string
*/
public $Username = '';
/**
* SMTP password.
* @var string
*/
public $Password = '';
/**
* SMTP auth type.
* Options are CRAM-MD5, LOGIN, PLAIN, NTLM, XOAUTH2, attempted in that order if not specified
* @var string
*/
public $AuthType = '';
/**
* SMTP realm.
* Used for NTLM auth
* @var string
*/
public $Realm = '';
/**
* SMTP workstation.
* Used for NTLM auth
* @var string
*/
public $Workstation = '';
/**
* The SMTP server timeout in seconds.
* Default of 5 minutes (300sec) is from RFC2821 section 4.5.3.2
* @var integer
*/
public $Timeout = 300;
/**
* SMTP class debug output mode.
* Debug output level.
* Options:
* * `0` No output
* * `1` Commands
* * `2` Data and commands
* * `3` As 2 plus connection status
* * `4` Low-level data output
* @var integer
* @see SMTP::$do_debug
*/
public $SMTPDebug = 0;
/**
* How to handle debug output.
* Options:
* * `echo` Output plain-text as-is, appropriate for CLI
* * `html` Output escaped, line breaks converted to ` `, appropriate for browser output
* * `error_log` Output to error log as configured in php.ini
*
* Alternatively, you can provide a callable expecting two params: a message string and the debug level:
*
* $mail->Debugoutput = function($str, $level) {echo "debug level $level; message: $str";};
*
* @var string|callable
* @see SMTP::$Debugoutput
*/
public $Debugoutput = 'echo';
/**
* Whether to keep SMTP connection open after each message.
* If this is set to true then to close the connection
* requires an explicit call to smtpClose().
* @var boolean
*/
public $SMTPKeepAlive = false;
/**
* Whether to split multiple to addresses into multiple messages
* or send them all in one message.
* Only supported in `mail` and `sendmail` transports, not in SMTP.
* @var boolean
*/
public $SingleTo = false;
/**
* Storage for addresses when SingleTo is enabled.
* @var array
* @TODO This should really not be public
*/
public $SingleToArray = array();
/**
* Whether to generate VERP addresses on send.
* Only applicable when sending via SMTP.
* @link https://en.wikipedia.org/wiki/Variable_envelope_return_path
* @link http://www.postfix.org/VERP_README.html Postfix VERP info
* @var boolean
*/
public $do_verp = false;
/**
* Whether to allow sending messages with an empty body.
* @var boolean
*/
public $AllowEmpty = false;
/**
* The default line ending.
* @note The default remains "\n". We force CRLF where we know
* it must be used via self::CRLF.
* @var string
*/
public $LE = "\n";
/**
* DKIM selector.
* @var string
*/
public $DKIM_selector = '';
/**
* DKIM Identity.
* Usually the email address used as the source of the email.
* @var string
*/
public $DKIM_identity = '';
/**
* DKIM passphrase.
* Used if your key is encrypted.
* @var string
*/
public $DKIM_passphrase = '';
/**
* DKIM signing domain name.
* @example 'example.com'
* @var string
*/
public $DKIM_domain = '';
/**
* DKIM private key file path.
* @var string
*/
public $DKIM_private = '';
/**
* DKIM private key string.
* If set, takes precedence over `$DKIM_private`.
* @var string
*/
public $DKIM_private_string = '';
/**
* Callback Action function name.
*
* The function that handles the result of the send email action.
* It is called out by send() for each email sent.
*
* Value can be any php callable: http://www.php.net/is_callable
*
* Parameters:
* boolean $result result of the send action
* array $to email addresses of the recipients
* array $cc cc email addresses
* array $bcc bcc email addresses
* string $subject the subject
* string $body the email body
* string $from email address of sender
* @var string
*/
public $action_function = '';
/**
* What to put in the X-Mailer header.
* Options: An empty string for PHPMailer default, whitespace for none, or a string to use
* @var string
*/
public $XMailer = '';
/**
* Which validator to use by default when validating email addresses.
* May be a callable to inject your own validator, but there are several built-in validators.
* @see PHPMailer::validateAddress()
* @var string|callable
* @static
*/
public static $validator = 'auto';
/**
* An instance of the SMTP sender class.
* @var SMTP
* @access protected
*/
protected $smtp = null;
/**
* The array of 'to' names and addresses.
* @var array
* @access protected
*/
protected $to = array();
/**
* The array of 'cc' names and addresses.
* @var array
* @access protected
*/
protected $cc = array();
/**
* The array of 'bcc' names and addresses.
* @var array
* @access protected
*/
protected $bcc = array();
/**
* The array of reply-to names and addresses.
* @var array
* @access protected
*/
protected $ReplyTo = array();
/**
* An array of all kinds of addresses.
* Includes all of $to, $cc, $bcc
* @var array
* @access protected
* @see PHPMailer::$to @see PHPMailer::$cc @see PHPMailer::$bcc
*/
protected $all_recipients = array();
/**
* An array of names and addresses queued for validation.
* In send(), valid and non duplicate entries are moved to $all_recipients
* and one of $to, $cc, or $bcc.
* This array is used only for addresses with IDN.
* @var array
* @access protected
* @see PHPMailer::$to @see PHPMailer::$cc @see PHPMailer::$bcc
* @see PHPMailer::$all_recipients
*/
protected $RecipientsQueue = array();
/**
* An array of reply-to names and addresses queued for validation.
* In send(), valid and non duplicate entries are moved to $ReplyTo.
* This array is used only for addresses with IDN.
* @var array
* @access protected
* @see PHPMailer::$ReplyTo
*/
protected $ReplyToQueue = array();
/**
* The array of attachments.
* @var array
* @access protected
*/
protected $attachment = array();
/**
* The array of custom headers.
* @var array
* @access protected
*/
protected $CustomHeader = array();
/**
* The most recent Message-ID (including angular brackets).
* @var string
* @access protected
*/
protected $lastMessageID = '';
/**
* The message's MIME type.
* @var string
* @access protected
*/
protected $message_type = '';
/**
* The array of MIME boundary strings.
* @var array
* @access protected
*/
protected $boundary = array();
/**
* The array of available languages.
* @var array
* @access protected
*/
protected $language = array();
/**
* The number of errors encountered.
* @var integer
* @access protected
*/
protected $error_count = 0;
/**
* The S/MIME certificate file path.
* @var string
* @access protected
*/
protected $sign_cert_file = '';
/**
* The S/MIME key file path.
* @var string
* @access protected
*/
protected $sign_key_file = '';
/**
* The optional S/MIME extra certificates ("CA Chain") file path.
* @var string
* @access protected
*/
protected $sign_extracerts_file = '';
/**
* The S/MIME password for the key.
* Used only if the key is encrypted.
* @var string
* @access protected
*/
protected $sign_key_pass = '';
/**
* Whether to throw exceptions for errors.
* @var boolean
* @access protected
*/
protected $exceptions = false;
/**
* Unique ID used for message ID and boundaries.
* @var string
* @access protected
*/
protected $uniqueid = '';
/**
* Error severity: message only, continue processing.
*/
const STOP_MESSAGE = 0;
/**
* Error severity: message, likely ok to continue processing.
*/
const STOP_CONTINUE = 1;
/**
* Error severity: message, plus full stop, critical error reached.
*/
const STOP_CRITICAL = 2;
/**
* SMTP RFC standard line ending.
*/
const CRLF = "\r\n";
/**
* The maximum line length allowed by RFC 2822 section 2.1.1
* @var integer
*/
const MAX_LINE_LENGTH = 998;
/**
* Constructor.
* @param boolean $exceptions Should we throw external exceptions?
*/
public function __construct($exceptions = null)
{
if ($exceptions !== null) {
$this->exceptions = (boolean)$exceptions;
}
//Pick an appropriate debug output format automatically
$this->Debugoutput = (strpos(PHP_SAPI, 'cli') !== false ? 'echo' : 'html');
}
/**
* Destructor.
*/
public function __destruct()
{
//Close any open SMTP connection nicely
$this->smtpClose();
}
/**
* Call mail() in a safe_mode-aware fashion.
* Also, unless sendmail_path points to sendmail (or something that
* claims to be sendmail), don't pass params (not a perfect fix,
* but it will do)
* @param string $to To
* @param string $subject Subject
* @param string $body Message Body
* @param string $header Additional Header(s)
* @param string $params Params
* @access private
* @return boolean
*/
private function mailPassthru($to, $subject, $body, $header, $params)
{
//Check overloading of mail function to avoid double-encoding
if (ini_get('mbstring.func_overload') & 1) {
$subject = $this->secureHeader($subject);
} else {
$subject = $this->encodeHeader($this->secureHeader($subject));
}
//Can't use additional_parameters in safe_mode, calling mail() with null params breaks
//@link http://php.net/manual/en/function.mail.php
if (ini_get('safe_mode') or !$this->UseSendmailOptions or is_null($params)) {
$result = @mail($to, $subject, $body, $header);
} else {
$result = @mail($to, $subject, $body, $header, $params);
}
return $result;
}
/**
* Output debugging info via user-defined method.
* Only generates output if SMTP debug output is enabled (@see SMTP::$do_debug).
* @see PHPMailer::$Debugoutput
* @see PHPMailer::$SMTPDebug
* @param string $str
*/
protected function edebug($str)
{
if ($this->SMTPDebug <= 0) {
return;
}
//Avoid clash with built-in function names
if (!in_array($this->Debugoutput, array('error_log', 'html', 'echo')) and is_callable($this->Debugoutput)) {
call_user_func($this->Debugoutput, $str, $this->SMTPDebug);
return;
}
switch ($this->Debugoutput) {
case 'error_log':
//Don't output, just log
error_log($str);
break;
case 'html':
//Cleans up output a bit for a better looking, HTML-safe output
echo htmlentities(
preg_replace('/[\r\n]+/', '', $str),
ENT_QUOTES,
'UTF-8'
)
. " \n";
break;
case 'echo':
default:
//Normalize line breaks
$str = preg_replace('/\r\n?/ms', "\n", $str);
echo gmdate('Y-m-d H:i:s') . "\t" . str_replace(
"\n",
"\n \t ",
trim($str)
) . "\n";
}
}
/**
* Sets message type to HTML or plain.
* @param boolean $isHtml True for HTML mode.
* @return void
*/
public function isHTML($isHtml = true)
{
if ($isHtml) {
$this->ContentType = 'text/html';
} else {
$this->ContentType = 'text/plain';
}
}
/**
* Send messages using SMTP.
* @return void
*/
public function isSMTP()
{
$this->Mailer = 'smtp';
}
/**
* Send messages using PHP's mail() function.
* @return void
*/
public function isMail()
{
$this->Mailer = 'mail';
}
/**
* Send messages using $Sendmail.
* @return void
*/
public function isSendmail()
{
$ini_sendmail_path = ini_get('sendmail_path');
if (!stristr($ini_sendmail_path, 'sendmail')) {
$this->Sendmail = '/usr/sbin/sendmail';
} else {
$this->Sendmail = $ini_sendmail_path;
}
$this->Mailer = 'sendmail';
}
/**
* Send messages using qmail.
* @return void
*/
public function isQmail()
{
$ini_sendmail_path = ini_get('sendmail_path');
if (!stristr($ini_sendmail_path, 'qmail')) {
$this->Sendmail = '/var/qmail/bin/qmail-inject';
} else {
$this->Sendmail = $ini_sendmail_path;
}
$this->Mailer = 'qmail';
}
/**
* Add a "To" address.
* @param string $address The email address to send to
* @param string $name
* @return boolean true on success, false if address already used or invalid in some way
*/
public function addAddress($address, $name = '')
{
return $this->addOrEnqueueAnAddress('to', $address, $name);
}
/**
* Add a "CC" address.
* @note: This function works with the SMTP mailer on win32, not with the "mail" mailer.
* @param string $address The email address to send to
* @param string $name
* @return boolean true on success, false if address already used or invalid in some way
*/
public function addCC($address, $name = '')
{
return $this->addOrEnqueueAnAddress('cc', $address, $name);
}
/**
* Add a "BCC" address.
* @note: This function works with the SMTP mailer on win32, not with the "mail" mailer.
* @param string $address The email address to send to
* @param string $name
* @return boolean true on success, false if address already used or invalid in some way
*/
public function addBCC($address, $name = '')
{
return $this->addOrEnqueueAnAddress('bcc', $address, $name);
}
/**
* Add a "Reply-To" address.
* @param string $address The email address to reply to
* @param string $name
* @return boolean true on success, false if address already used or invalid in some way
*/
public function addReplyTo($address, $name = '')
{
return $this->addOrEnqueueAnAddress('Reply-To', $address, $name);
}
/**
* Add an address to one of the recipient arrays or to the ReplyTo array. Because PHPMailer
* can't validate addresses with an IDN without knowing the PHPMailer::$CharSet (that can still
* be modified after calling this function), addition of such addresses is delayed until send().
* Addresses that have been added already return false, but do not throw exceptions.
* @param string $kind One of 'to', 'cc', 'bcc', or 'ReplyTo'
* @param string $address The email address to send, resp. to reply to
* @param string $name
* @throws phpmailerException
* @return boolean true on success, false if address already used or invalid in some way
* @access protected
*/
protected function addOrEnqueueAnAddress($kind, $address, $name)
{
$address = trim($address);
$name = trim(preg_replace('/[\r\n]+/', '', $name)); //Strip breaks and trim
if (($pos = strrpos($address, '@')) === false) {
// At-sign is misssing.
$error_message = $this->lang('invalid_address') . " (addAnAddress $kind): $address";
$this->setError($error_message);
$this->edebug($error_message);
if ($this->exceptions) {
throw new phpmailerException($error_message);
}
return false;
}
$params = array($kind, $address, $name);
// Enqueue addresses with IDN until we know the PHPMailer::$CharSet.
if ($this->has8bitChars(substr($address, ++$pos)) and $this->idnSupported()) {
if ($kind != 'Reply-To') {
if (!array_key_exists($address, $this->RecipientsQueue)) {
$this->RecipientsQueue[$address] = $params;
return true;
}
} else {
if (!array_key_exists($address, $this->ReplyToQueue)) {
$this->ReplyToQueue[$address] = $params;
return true;
}
}
return false;
}
// Immediately add standard addresses without IDN.
return call_user_func_array(array($this, 'addAnAddress'), $params);
}
/**
* Add an address to one of the recipient arrays or to the ReplyTo array.
* Addresses that have been added already return false, but do not throw exceptions.
* @param string $kind One of 'to', 'cc', 'bcc', or 'ReplyTo'
* @param string $address The email address to send, resp. to reply to
* @param string $name
* @throws phpmailerException
* @return boolean true on success, false if address already used or invalid in some way
* @access protected
*/
protected function addAnAddress($kind, $address, $name = '')
{
if (!in_array($kind, array('to', 'cc', 'bcc', 'Reply-To'))) {
$error_message = $this->lang('Invalid recipient kind: ') . $kind;
$this->setError($error_message);
$this->edebug($error_message);
if ($this->exceptions) {
throw new phpmailerException($error_message);
}
return false;
}
if (!$this->validateAddress($address)) {
$error_message = $this->lang('invalid_address') . " (addAnAddress $kind): $address";
$this->setError($error_message);
$this->edebug($error_message);
if ($this->exceptions) {
throw new phpmailerException($error_message);
}
return false;
}
if ($kind != 'Reply-To') {
if (!array_key_exists(strtolower($address), $this->all_recipients)) {
array_push($this->$kind, array($address, $name));
$this->all_recipients[strtolower($address)] = true;
return true;
}
} else {
if (!array_key_exists(strtolower($address), $this->ReplyTo)) {
$this->ReplyTo[strtolower($address)] = array($address, $name);
return true;
}
}
return false;
}
/**
* Parse and validate a string containing one or more RFC822-style comma-separated email addresses
* of the form "display name " into an array of name/address pairs.
* Uses the imap_rfc822_parse_adrlist function if the IMAP extension is available.
* Note that quotes in the name part are removed.
* @param string $addrstr The address list string
* @param bool $useimap Whether to use the IMAP extension to parse the list
* @return array
* @link http://www.andrew.cmu.edu/user/agreen1/testing/mrbs/web/Mail/RFC822.php A more careful implementation
*/
public function parseAddresses($addrstr, $useimap = true)
{
$addresses = array();
if ($useimap and function_exists('imap_rfc822_parse_adrlist')) {
//Use this built-in parser if it's available
$list = imap_rfc822_parse_adrlist($addrstr, '');
foreach ($list as $address) {
if ($address->host != '.SYNTAX-ERROR.') {
if ($this->validateAddress($address->mailbox . '@' . $address->host)) {
$addresses[] = array(
'name' => (property_exists($address, 'personal') ? $address->personal : ''),
'address' => $address->mailbox . '@' . $address->host
);
}
}
}
} else {
//Use this simpler parser
$list = explode(',', $addrstr);
foreach ($list as $address) {
$address = trim($address);
//Is there a separate name part?
if (strpos($address, '<') === false) {
//No separate name, just use the whole thing
if ($this->validateAddress($address)) {
$addresses[] = array(
'name' => '',
'address' => $address
);
}
} else {
list($name, $email) = explode('<', $address);
$email = trim(str_replace('>', '', $email));
if ($this->validateAddress($email)) {
$addresses[] = array(
'name' => trim(str_replace(array('"', "'"), '', $name)),
'address' => $email
);
}
}
}
}
return $addresses;
}
/**
* Set the From and FromName properties.
* @param string $address
* @param string $name
* @param boolean $auto Whether to also set the Sender address, defaults to true
* @throws phpmailerException
* @return boolean
*/
public function setFrom($address, $name = '', $auto = true)
{
$address = trim($address);
$name = trim(preg_replace('/[\r\n]+/', '', $name)); //Strip breaks and trim
// Don't validate now addresses with IDN. Will be done in send().
if (($pos = strrpos($address, '@')) === false or
(!$this->has8bitChars(substr($address, ++$pos)) or !$this->idnSupported()) and
!$this->validateAddress($address)) {
$error_message = $this->lang('invalid_address') . " (setFrom) $address";
$this->setError($error_message);
$this->edebug($error_message);
if ($this->exceptions) {
throw new phpmailerException($error_message);
}
return false;
}
$this->From = $address;
$this->FromName = $name;
if ($auto) {
if (empty($this->Sender)) {
$this->Sender = $address;
}
}
return true;
}
/**
* Return the Message-ID header of the last email.
* Technically this is the value from the last time the headers were created,
* but it's also the message ID of the last sent message except in
* pathological cases.
* @return string
*/
public function getLastMessageID()
{
return $this->lastMessageID;
}
/**
* Check that a string looks like an email address.
* @param string $address The email address to check
* @param string|callable $patternselect A selector for the validation pattern to use :
* * `auto` Pick best pattern automatically;
* * `pcre8` Use the squiloople.com pattern, requires PCRE > 8.0, PHP >= 5.3.2, 5.2.14;
* * `pcre` Use old PCRE implementation;
* * `php` Use PHP built-in FILTER_VALIDATE_EMAIL;
* * `html5` Use the pattern given by the HTML5 spec for 'email' type form input elements.
* * `noregex` Don't use a regex: super fast, really dumb.
* Alternatively you may pass in a callable to inject your own validator, for example:
* PHPMailer::validateAddress('user@example.com', function($address) {
* return (strpos($address, '@') !== false);
* });
* You can also set the PHPMailer::$validator static to a callable, allowing built-in methods to use your validator.
* @return boolean
* @static
* @access public
*/
public static function validateAddress($address, $patternselect = null)
{
if (is_null($patternselect)) {
$patternselect = self::$validator;
}
if (is_callable($patternselect)) {
return call_user_func($patternselect, $address);
}
//Reject line breaks in addresses; it's valid RFC5322, but not RFC5321
if (strpos($address, "\n") !== false or strpos($address, "\r") !== false) {
return false;
}
if (!$patternselect or $patternselect == 'auto') {
//Check this constant first so it works when extension_loaded() is disabled by safe mode
//Constant was added in PHP 5.2.4
if (defined('PCRE_VERSION')) {
//This pattern can get stuck in a recursive loop in PCRE <= 8.0.2
if (version_compare(PCRE_VERSION, '8.0.3') >= 0) {
$patternselect = 'pcre8';
} else {
$patternselect = 'pcre';
}
} elseif (function_exists('extension_loaded') and extension_loaded('pcre')) {
//Fall back to older PCRE
$patternselect = 'pcre';
} else {
//Filter_var appeared in PHP 5.2.0 and does not require the PCRE extension
if (version_compare(PHP_VERSION, '5.2.0') >= 0) {
$patternselect = 'php';
} else {
$patternselect = 'noregex';
}
}
}
switch ($patternselect) {
case 'pcre8':
/**
* Uses the same RFC5322 regex on which FILTER_VALIDATE_EMAIL is based, but allows dotless domains.
* @link http://squiloople.com/2009/12/20/email-address-validation/
* @copyright 2009-2010 Michael Rushton
* Feel free to use and redistribute this code. But please keep this copyright notice.
*/
return (boolean)preg_match(
'/^(?!(?>(?1)"?(?>\\\[ -~]|[^"])"?(?1)){255,})(?!(?>(?1)"?(?>\\\[ -~]|[^"])"?(?1)){65,}@)' .
'((?>(?>(?>((?>(?>(?>\x0D\x0A)?[\t ])+|(?>[\t ]*\x0D\x0A)?[\t ]+)?)(\((?>(?2)' .
'(?>[\x01-\x08\x0B\x0C\x0E-\'*-\[\]-\x7F]|\\\[\x00-\x7F]|(?3)))*(?2)\)))+(?2))|(?2))?)' .
'([!#-\'*+\/-9=?^-~-]+|"(?>(?2)(?>[\x01-\x08\x0B\x0C\x0E-!#-\[\]-\x7F]|\\\[\x00-\x7F]))*' .
'(?2)")(?>(?1)\.(?1)(?4))*(?1)@(?!(?1)[a-z0-9-]{64,})(?1)(?>([a-z0-9](?>[a-z0-9-]*[a-z0-9])?)' .
'(?>(?1)\.(?!(?1)[a-z0-9-]{64,})(?1)(?5)){0,126}|\[(?:(?>IPv6:(?>([a-f0-9]{1,4})(?>:(?6)){7}' .
'|(?!(?:.*[a-f0-9][:\]]){8,})((?6)(?>:(?6)){0,6})?::(?7)?))|(?>(?>IPv6:(?>(?6)(?>:(?6)){5}:' .
'|(?!(?:.*[a-f0-9]:){6,})(?8)?::(?>((?6)(?>:(?6)){0,4}):)?))?(25[0-5]|2[0-4][0-9]|1[0-9]{2}' .
'|[1-9]?[0-9])(?>\.(?9)){3}))\])(?1)$/isD',
$address
);
case 'pcre':
//An older regex that doesn't need a recent PCRE
return (boolean)preg_match(
'/^(?!(?>"?(?>\\\[ -~]|[^"])"?){255,})(?!(?>"?(?>\\\[ -~]|[^"])"?){65,}@)(?>' .
'[!#-\'*+\/-9=?^-~-]+|"(?>(?>[\x01-\x08\x0B\x0C\x0E-!#-\[\]-\x7F]|\\\[\x00-\xFF]))*")' .
'(?>\.(?>[!#-\'*+\/-9=?^-~-]+|"(?>(?>[\x01-\x08\x0B\x0C\x0E-!#-\[\]-\x7F]|\\\[\x00-\xFF]))*"))*' .
'@(?>(?![a-z0-9-]{64,})(?>[a-z0-9](?>[a-z0-9-]*[a-z0-9])?)(?>\.(?![a-z0-9-]{64,})' .
'(?>[a-z0-9](?>[a-z0-9-]*[a-z0-9])?)){0,126}|\[(?:(?>IPv6:(?>(?>[a-f0-9]{1,4})(?>:' .
'[a-f0-9]{1,4}){7}|(?!(?:.*[a-f0-9][:\]]){8,})(?>[a-f0-9]{1,4}(?>:[a-f0-9]{1,4}){0,6})?' .
'::(?>[a-f0-9]{1,4}(?>:[a-f0-9]{1,4}){0,6})?))|(?>(?>IPv6:(?>[a-f0-9]{1,4}(?>:' .
'[a-f0-9]{1,4}){5}:|(?!(?:.*[a-f0-9]:){6,})(?>[a-f0-9]{1,4}(?>:[a-f0-9]{1,4}){0,4})?' .
'::(?>(?:[a-f0-9]{1,4}(?>:[a-f0-9]{1,4}){0,4}):)?))?(?>25[0-5]|2[0-4][0-9]|1[0-9]{2}' .
'|[1-9]?[0-9])(?>\.(?>25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3}))\])$/isD',
$address
);
case 'html5':
/**
* This is the pattern used in the HTML5 spec for validation of 'email' type form input elements.
* @link http://www.whatwg.org/specs/web-apps/current-work/#e-mail-state-(type=email)
*/
return (boolean)preg_match(
'/^[a-zA-Z0-9.!#$%&\'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}' .
'[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/sD',
$address
);
case 'noregex':
//No PCRE! Do something _very_ approximate!
//Check the address is 3 chars or longer and contains an @ that's not the first or last char
return (strlen($address) >= 3
and strpos($address, '@') >= 1
and strpos($address, '@') != strlen($address) - 1);
case 'php':
default:
return (boolean)filter_var($address, FILTER_VALIDATE_EMAIL);
}
}
/**
* Tells whether IDNs (Internationalized Domain Names) are supported or not. This requires the
* "intl" and "mbstring" PHP extensions.
* @return bool "true" if required functions for IDN support are present
*/
public function idnSupported()
{
// @TODO: Write our own "idn_to_ascii" function for PHP <= 5.2.
return function_exists('idn_to_ascii') and function_exists('mb_convert_encoding');
}
/**
* Converts IDN in given email address to its ASCII form, also known as punycode, if possible.
* Important: Address must be passed in same encoding as currently set in PHPMailer::$CharSet.
* This function silently returns unmodified address if:
* - No conversion is necessary (i.e. domain name is not an IDN, or is already in ASCII form)
* - Conversion to punycode is impossible (e.g. required PHP functions are not available)
* or fails for any reason (e.g. domain has characters not allowed in an IDN)
* @see PHPMailer::$CharSet
* @param string $address The email address to convert
* @return string The encoded address in ASCII form
*/
public function punyencodeAddress($address)
{
// Verify we have required functions, CharSet, and at-sign.
if ($this->idnSupported() and
!empty($this->CharSet) and
($pos = strrpos($address, '@')) !== false) {
$domain = substr($address, ++$pos);
// Verify CharSet string is a valid one, and domain properly encoded in this CharSet.
if ($this->has8bitChars($domain) and @mb_check_encoding($domain, $this->CharSet)) {
$domain = mb_convert_encoding($domain, 'UTF-8', $this->CharSet);
if (($punycode = defined('INTL_IDNA_VARIANT_UTS46') ?
idn_to_ascii($domain, 0, INTL_IDNA_VARIANT_UTS46) :
idn_to_ascii($domain)) !== false) {
return substr($address, 0, $pos) . $punycode;
}
}
}
return $address;
}
/**
* Create a message and send it.
* Uses the sending method specified by $Mailer.
* @throws phpmailerException
* @return boolean false on error - See the ErrorInfo property for details of the error.
*/
public function send()
{
try {
if (!$this->preSend()) {
return false;
}
return $this->postSend();
} catch (phpmailerException $exc) {
$this->mailHeader = '';
$this->setError($exc->getMessage());
if ($this->exceptions) {
throw $exc;
}
return false;
}
}
/**
* Prepare a message for sending.
* @throws phpmailerException
* @return boolean
*/
public function preSend()
{
try {
$this->error_count = 0; // Reset errors
$this->mailHeader = '';
// Dequeue recipient and Reply-To addresses with IDN
foreach (array_merge($this->RecipientsQueue, $this->ReplyToQueue) as $params) {
$params[1] = $this->punyencodeAddress($params[1]);
call_user_func_array(array($this, 'addAnAddress'), $params);
}
if ((count($this->to) + count($this->cc) + count($this->bcc)) < 1) {
throw new phpmailerException($this->lang('provide_address'), self::STOP_CRITICAL);
}
// Validate From, Sender, and ConfirmReadingTo addresses
foreach (array('From', 'Sender', 'ConfirmReadingTo') as $address_kind) {
$this->$address_kind = trim($this->$address_kind);
if (empty($this->$address_kind)) {
continue;
}
$this->$address_kind = $this->punyencodeAddress($this->$address_kind);
if (!$this->validateAddress($this->$address_kind)) {
$error_message = $this->lang('invalid_address') . ' (punyEncode) ' . $this->$address_kind;
$this->setError($error_message);
$this->edebug($error_message);
if ($this->exceptions) {
throw new phpmailerException($error_message);
}
return false;
}
}
// Set whether the message is multipart/alternative
if ($this->alternativeExists()) {
$this->ContentType = 'multipart/alternative';
}
$this->setMessageType();
// Refuse to send an empty message unless we are specifically allowing it
if (!$this->AllowEmpty and empty($this->Body)) {
throw new phpmailerException($this->lang('empty_message'), self::STOP_CRITICAL);
}
// Create body before headers in case body makes changes to headers (e.g. altering transfer encoding)
$this->MIMEHeader = '';
$this->MIMEBody = $this->createBody();
// createBody may have added some headers, so retain them
$tempheaders = $this->MIMEHeader;
$this->MIMEHeader = $this->createHeader();
$this->MIMEHeader .= $tempheaders;
// To capture the complete message when using mail(), create
// an extra header list which createHeader() doesn't fold in
if ($this->Mailer == 'mail') {
if (count($this->to) > 0) {
$this->mailHeader .= $this->addrAppend('To', $this->to);
} else {
$this->mailHeader .= $this->headerLine('To', 'undisclosed-recipients:;');
}
$this->mailHeader .= $this->headerLine(
'Subject',
$this->encodeHeader($this->secureHeader(trim($this->Subject)))
);
}
// Sign with DKIM if enabled
if (!empty($this->DKIM_domain)
&& !empty($this->DKIM_selector)
&& (!empty($this->DKIM_private_string)
|| (!empty($this->DKIM_private) && file_exists($this->DKIM_private))
)
) {
$header_dkim = $this->DKIM_Add(
$this->MIMEHeader . $this->mailHeader,
$this->encodeHeader($this->secureHeader($this->Subject)),
$this->MIMEBody
);
$this->MIMEHeader = rtrim($this->MIMEHeader, "\r\n ") . self::CRLF .
str_replace("\r\n", "\n", $header_dkim) . self::CRLF;
}
return true;
} catch (phpmailerException $exc) {
$this->setError($exc->getMessage());
if ($this->exceptions) {
throw $exc;
}
return false;
}
}
/**
* Actually send a message.
* Send the email via the selected mechanism
* @throws phpmailerException
* @return boolean
*/
public function postSend()
{
try {
// Choose the mailer and send through it
switch ($this->Mailer) {
case 'sendmail':
case 'qmail':
return $this->sendmailSend($this->MIMEHeader, $this->MIMEBody);
case 'smtp':
return $this->smtpSend($this->MIMEHeader, $this->MIMEBody);
case 'mail':
return $this->mailSend($this->MIMEHeader, $this->MIMEBody);
default:
$sendMethod = $this->Mailer.'Send';
if (method_exists($this, $sendMethod)) {
return $this->$sendMethod($this->MIMEHeader, $this->MIMEBody);
}
return $this->mailSend($this->MIMEHeader, $this->MIMEBody);
}
} catch (phpmailerException $exc) {
$this->setError($exc->getMessage());
$this->edebug($exc->getMessage());
if ($this->exceptions) {
throw $exc;
}
}
return false;
}
/**
* Send mail using the $Sendmail program.
* @param string $header The message headers
* @param string $body The message body
* @see PHPMailer::$Sendmail
* @throws phpmailerException
* @access protected
* @return boolean
*/
protected function sendmailSend($header, $body)
{
// CVE-2016-10033, CVE-2016-10045: Don't pass -f if characters will be escaped.
if (!empty($this->Sender) and self::isShellSafe($this->Sender)) {
if ($this->Mailer == 'qmail') {
$sendmailFmt = '%s -f%s';
} else {
$sendmailFmt = '%s -oi -f%s -t';
}
} else {
if ($this->Mailer == 'qmail') {
$sendmailFmt = '%s';
} else {
$sendmailFmt = '%s -oi -t';
}
}
// TODO: If possible, this should be changed to escapeshellarg. Needs thorough testing.
$sendmail = sprintf($sendmailFmt, escapeshellcmd($this->Sendmail), $this->Sender);
if ($this->SingleTo) {
foreach ($this->SingleToArray as $toAddr) {
if (!@$mail = popen($sendmail, 'w')) {
throw new phpmailerException($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL);
}
fputs($mail, 'To: ' . $toAddr . "\n");
fputs($mail, $header);
fputs($mail, $body);
$result = pclose($mail);
$this->doCallback(
($result == 0),
array($toAddr),
$this->cc,
$this->bcc,
$this->Subject,
$body,
$this->From
);
if ($result != 0) {
throw new phpmailerException($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL);
}
}
} else {
if (!@$mail = popen($sendmail, 'w')) {
throw new phpmailerException($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL);
}
fputs($mail, $header);
fputs($mail, $body);
$result = pclose($mail);
$this->doCallback(
($result == 0),
$this->to,
$this->cc,
$this->bcc,
$this->Subject,
$body,
$this->From
);
if ($result != 0) {
throw new phpmailerException($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL);
}
}
return true;
}
/**
* Fix CVE-2016-10033 and CVE-2016-10045 by disallowing potentially unsafe shell characters.
*
* Note that escapeshellarg and escapeshellcmd are inadequate for our purposes, especially on Windows.
* @param string $string The string to be validated
* @see https://github.com/PHPMailer/PHPMailer/issues/924 CVE-2016-10045 bug report
* @access protected
* @return boolean
*/
protected static function isShellSafe($string)
{
// Future-proof
if (escapeshellcmd($string) !== $string
or !in_array(escapeshellarg($string), array("'$string'", "\"$string\""))
) {
return false;
}
$length = strlen($string);
for ($i = 0; $i < $length; $i++) {
$c = $string[$i];
// All other characters have a special meaning in at least one common shell, including = and +.
// Full stop (.) has a special meaning in cmd.exe, but its impact should be negligible here.
// Note that this does permit non-Latin alphanumeric characters based on the current locale.
if (!ctype_alnum($c) && strpos('@_-.', $c) === false) {
return false;
}
}
return true;
}
/**
* Send mail using the PHP mail() function.
* @param string $header The message headers
* @param string $body The message body
* @link http://www.php.net/manual/en/book.mail.php
* @throws phpmailerException
* @access protected
* @return boolean
*/
protected function mailSend($header, $body)
{
$toArr = array();
foreach ($this->to as $toaddr) {
$toArr[] = $this->addrFormat($toaddr);
}
$to = implode(', ', $toArr);
$params = null;
//This sets the SMTP envelope sender which gets turned into a return-path header by the receiver
if (!empty($this->Sender) and $this->validateAddress($this->Sender)) {
// CVE-2016-10033, CVE-2016-10045: Don't pass -f if characters will be escaped.
if (self::isShellSafe($this->Sender)) {
$params = sprintf('-f%s', $this->Sender);
}
}
if (!empty($this->Sender) and !ini_get('safe_mode') and $this->validateAddress($this->Sender)) {
$old_from = ini_get('sendmail_from');
ini_set('sendmail_from', $this->Sender);
}
$result = false;
if ($this->SingleTo and count($toArr) > 1) {
foreach ($toArr as $toAddr) {
$result = $this->mailPassthru($toAddr, $this->Subject, $body, $header, $params);
$this->doCallback($result, array($toAddr), $this->cc, $this->bcc, $this->Subject, $body, $this->From);
}
} else {
$result = $this->mailPassthru($to, $this->Subject, $body, $header, $params);
$this->doCallback($result, $this->to, $this->cc, $this->bcc, $this->Subject, $body, $this->From);
}
if (isset($old_from)) {
ini_set('sendmail_from', $old_from);
}
if (!$result) {
throw new phpmailerException($this->lang('instantiate'), self::STOP_CRITICAL);
}
return true;
}
/**
* Get an instance to use for SMTP operations.
* Override this function to load your own SMTP implementation
* @return SMTP
*/
public function getSMTPInstance()
{
if (!is_object($this->smtp)) {
$this->smtp = new SMTP;
}
return $this->smtp;
}
/**
* Send mail via SMTP.
* Returns false if there is a bad MAIL FROM, RCPT, or DATA input.
* Uses the PHPMailerSMTP class by default.
* @see PHPMailer::getSMTPInstance() to use a different class.
* @param string $header The message headers
* @param string $body The message body
* @throws phpmailerException
* @uses SMTP
* @access protected
* @return boolean
*/
protected function smtpSend($header, $body)
{
$bad_rcpt = array();
if (!$this->smtpConnect($this->SMTPOptions)) {
throw new phpmailerException($this->lang('smtp_connect_failed'), self::STOP_CRITICAL);
}
if (!empty($this->Sender) and $this->validateAddress($this->Sender)) {
$smtp_from = $this->Sender;
} else {
$smtp_from = $this->From;
}
if (!$this->smtp->mail($smtp_from)) {
$this->setError($this->lang('from_failed') . $smtp_from . ' : ' . implode(',', $this->smtp->getError()));
throw new phpmailerException($this->ErrorInfo, self::STOP_CRITICAL);
}
// Attempt to send to all recipients
foreach (array($this->to, $this->cc, $this->bcc) as $togroup) {
foreach ($togroup as $to) {
if (!$this->smtp->recipient($to[0])) {
$error = $this->smtp->getError();
$bad_rcpt[] = array('to' => $to[0], 'error' => $error['detail']);
$isSent = false;
} else {
$isSent = true;
}
$this->doCallback($isSent, array($to[0]), array(), array(), $this->Subject, $body, $this->From);
}
}
// Only send the DATA command if we have viable recipients
if ((count($this->all_recipients) > count($bad_rcpt)) and !$this->smtp->data($header . $body)) {
throw new phpmailerException($this->lang('data_not_accepted'), self::STOP_CRITICAL);
}
if ($this->SMTPKeepAlive) {
$this->smtp->reset();
} else {
$this->smtp->quit();
$this->smtp->close();
}
//Create error message for any bad addresses
if (count($bad_rcpt) > 0) {
$errstr = '';
foreach ($bad_rcpt as $bad) {
$errstr .= $bad['to'] . ': ' . $bad['error'];
}
throw new phpmailerException(
$this->lang('recipients_failed') . $errstr,
self::STOP_CONTINUE
);
}
return true;
}
/**
* Initiate a connection to an SMTP server.
* Returns false if the operation failed.
* @param array $options An array of options compatible with stream_context_create()
* @uses SMTP
* @access public
* @throws phpmailerException
* @return boolean
*/
public function smtpConnect($options = null)
{
if (is_null($this->smtp)) {
$this->smtp = $this->getSMTPInstance();
}
//If no options are provided, use whatever is set in the instance
if (is_null($options)) {
$options = $this->SMTPOptions;
}
// Already connected?
if ($this->smtp->connected()) {
return true;
}
$this->smtp->setTimeout($this->Timeout);
$this->smtp->setDebugLevel($this->SMTPDebug);
$this->smtp->setDebugOutput($this->Debugoutput);
$this->smtp->setVerp($this->do_verp);
$hosts = explode(';', $this->Host);
$lastexception = null;
foreach ($hosts as $hostentry) {
$hostinfo = array();
if (!preg_match(
'/^((ssl|tls):\/\/)*([a-zA-Z0-9\.-]*|\[[a-fA-F0-9:]+\]):?([0-9]*)$/',
trim($hostentry),
$hostinfo
)) {
// Not a valid host entry
$this->edebug('Ignoring invalid host: ' . $hostentry);
continue;
}
// $hostinfo[2]: optional ssl or tls prefix
// $hostinfo[3]: the hostname
// $hostinfo[4]: optional port number
// The host string prefix can temporarily override the current setting for SMTPSecure
// If it's not specified, the default value is used
$prefix = '';
$secure = $this->SMTPSecure;
$tls = ($this->SMTPSecure == 'tls');
if ('ssl' == $hostinfo[2] or ('' == $hostinfo[2] and 'ssl' == $this->SMTPSecure)) {
$prefix = 'ssl://';
$tls = false; // Can't have SSL and TLS at the same time
$secure = 'ssl';
} elseif ($hostinfo[2] == 'tls') {
$tls = true;
// tls doesn't use a prefix
$secure = 'tls';
}
//Do we need the OpenSSL extension?
$sslext = defined('OPENSSL_ALGO_SHA1');
if ('tls' === $secure or 'ssl' === $secure) {
//Check for an OpenSSL constant rather than using extension_loaded, which is sometimes disabled
if (!$sslext) {
throw new phpmailerException($this->lang('extension_missing').'openssl', self::STOP_CRITICAL);
}
}
$host = $hostinfo[3];
$port = $this->Port;
$tport = (integer)$hostinfo[4];
if ($tport > 0 and $tport < 65536) {
$port = $tport;
}
if ($this->smtp->connect($prefix . $host, $port, $this->Timeout, $options)) {
try {
if ($this->Helo) {
$hello = $this->Helo;
} else {
$hello = $this->serverHostname();
}
$this->smtp->hello($hello);
//Automatically enable TLS encryption if:
// * it's not disabled
// * we have openssl extension
// * we are not already using SSL
// * the server offers STARTTLS
if ($this->SMTPAutoTLS and $sslext and $secure != 'ssl' and $this->smtp->getServerExt('STARTTLS')) {
$tls = true;
}
if ($tls) {
if (!$this->smtp->startTLS()) {
throw new phpmailerException($this->lang('connect_host'));
}
// We must resend EHLO after TLS negotiation
$this->smtp->hello($hello);
}
if ($this->SMTPAuth) {
if (!$this->smtp->authenticate(
$this->Username,
$this->Password,
$this->AuthType,
$this->Realm,
$this->Workstation
)
) {
throw new phpmailerException($this->lang('authenticate'));
}
}
return true;
} catch (phpmailerException $exc) {
$lastexception = $exc;
$this->edebug($exc->getMessage());
// We must have connected, but then failed TLS or Auth, so close connection nicely
$this->smtp->quit();
}
}
}
// If we get here, all connection attempts have failed, so close connection hard
$this->smtp->close();
// As we've caught all exceptions, just report whatever the last one was
if ($this->exceptions and !is_null($lastexception)) {
throw $lastexception;
}
return false;
}
/**
* Close the active SMTP session if one exists.
* @return void
*/
public function smtpClose()
{
if (is_a($this->smtp, 'SMTP')) {
if ($this->smtp->connected()) {
$this->smtp->quit();
$this->smtp->close();
}
}
}
/**
* Set the language for error messages.
* Returns false if it cannot load the language file.
* The default language is English.
* @param string $langcode ISO 639-1 2-character language code (e.g. French is "fr")
* @param string $lang_path Path to the language file directory, with trailing separator (slash)
* @return boolean
* @access public
*/
public function setLanguage($langcode = 'en', $lang_path = '')
{
// Backwards compatibility for renamed language codes
$renamed_langcodes = array(
'br' => 'pt_br',
'cz' => 'cs',
'dk' => 'da',
'no' => 'nb',
'se' => 'sv',
'sr' => 'rs'
);
if (isset($renamed_langcodes[$langcode])) {
$langcode = $renamed_langcodes[$langcode];
}
// Define full set of translatable strings in English
$PHPMAILER_LANG = array(
'authenticate' => 'SMTP Error: Could not authenticate.',
'connect_host' => 'SMTP Error: Could not connect to SMTP host.',
'data_not_accepted' => 'SMTP Error: data not accepted.',
'empty_message' => 'Message body empty',
'encoding' => 'Unknown encoding: ',
'execute' => 'Could not execute: ',
'file_access' => 'Could not access file: ',
'file_open' => 'File Error: Could not open file: ',
'from_failed' => 'The following From address failed: ',
'instantiate' => 'Could not instantiate mail function.',
'invalid_address' => 'Invalid address: ',
'mailer_not_supported' => ' mailer is not supported.',
'provide_address' => 'You must provide at least one recipient email address.',
'recipients_failed' => 'SMTP Error: The following recipients failed: ',
'signing' => 'Signing Error: ',
'smtp_connect_failed' => 'SMTP connect() failed.',
'smtp_error' => 'SMTP server error: ',
'variable_set' => 'Cannot set or reset variable: ',
'extension_missing' => 'Extension missing: '
);
if (empty($lang_path)) {
// Calculate an absolute path so it can work if CWD is not here
$lang_path = dirname(__FILE__). DIRECTORY_SEPARATOR . 'language'. DIRECTORY_SEPARATOR;
}
//Validate $langcode
if (!preg_match('/^[a-z]{2}(?:_[a-zA-Z]{2})?$/', $langcode)) {
$langcode = 'en';
}
$foundlang = true;
$lang_file = $lang_path . 'phpmailer.lang-' . $langcode . '.php';
// There is no English translation file
if ($langcode != 'en') {
// Make sure language file path is readable
if (!is_readable($lang_file)) {
$foundlang = false;
} else {
// Overwrite language-specific strings.
// This way we'll never have missing translation keys.
$foundlang = include $lang_file;
}
}
$this->language = $PHPMAILER_LANG;
return (boolean)$foundlang; // Returns false if language not found
}
/**
* Get the array of strings for the current language.
* @return array
*/
public function getTranslations()
{
return $this->language;
}
/**
* Create recipient headers.
* @access public
* @param string $type
* @param array $addr An array of recipient,
* where each recipient is a 2-element indexed array with element 0 containing an address
* and element 1 containing a name, like:
* array(array('joe@example.com', 'Joe User'), array('zoe@example.com', 'Zoe User'))
* @return string
*/
public function addrAppend($type, $addr)
{
$addresses = array();
foreach ($addr as $address) {
$addresses[] = $this->addrFormat($address);
}
return $type . ': ' . implode(', ', $addresses) . $this->LE;
}
/**
* Format an address for use in a message header.
* @access public
* @param array $addr A 2-element indexed array, element 0 containing an address, element 1 containing a name
* like array('joe@example.com', 'Joe User')
* @return string
*/
public function addrFormat($addr)
{
if (empty($addr[1])) { // No name provided
return $this->secureHeader($addr[0]);
} else {
return $this->encodeHeader($this->secureHeader($addr[1]), 'phrase') . ' <' . $this->secureHeader(
$addr[0]
) . '>';
}
}
/**
* Word-wrap message.
* For use with mailers that do not automatically perform wrapping
* and for quoted-printable encoded messages.
* Original written by philippe.
* @param string $message The message to wrap
* @param integer $length The line length to wrap to
* @param boolean $qp_mode Whether to run in Quoted-Printable mode
* @access public
* @return string
*/
public function wrapText($message, $length, $qp_mode = false)
{
if ($qp_mode) {
$soft_break = sprintf(' =%s', $this->LE);
} else {
$soft_break = $this->LE;
}
// If utf-8 encoding is used, we will need to make sure we don't
// split multibyte characters when we wrap
$is_utf8 = (strtolower($this->CharSet) == 'utf-8');
$lelen = strlen($this->LE);
$crlflen = strlen(self::CRLF);
$message = $this->fixEOL($message);
//Remove a trailing line break
if (substr($message, -$lelen) == $this->LE) {
$message = substr($message, 0, -$lelen);
}
//Split message into lines
$lines = explode($this->LE, $message);
//Message will be rebuilt in here
$message = '';
foreach ($lines as $line) {
$words = explode(' ', $line);
$buf = '';
$firstword = true;
foreach ($words as $word) {
if ($qp_mode and (strlen($word) > $length)) {
$space_left = $length - strlen($buf) - $crlflen;
if (!$firstword) {
if ($space_left > 20) {
$len = $space_left;
if ($is_utf8) {
$len = $this->utf8CharBoundary($word, $len);
} elseif (substr($word, $len - 1, 1) == '=') {
$len--;
} elseif (substr($word, $len - 2, 1) == '=') {
$len -= 2;
}
$part = substr($word, 0, $len);
$word = substr($word, $len);
$buf .= ' ' . $part;
$message .= $buf . sprintf('=%s', self::CRLF);
} else {
$message .= $buf . $soft_break;
}
$buf = '';
}
while (strlen($word) > 0) {
if ($length <= 0) {
break;
}
$len = $length;
if ($is_utf8) {
$len = $this->utf8CharBoundary($word, $len);
} elseif (substr($word, $len - 1, 1) == '=') {
$len--;
} elseif (substr($word, $len - 2, 1) == '=') {
$len -= 2;
}
$part = substr($word, 0, $len);
$word = substr($word, $len);
if (strlen($word) > 0) {
$message .= $part . sprintf('=%s', self::CRLF);
} else {
$buf = $part;
}
}
} else {
$buf_o = $buf;
if (!$firstword) {
$buf .= ' ';
}
$buf .= $word;
if (strlen($buf) > $length and $buf_o != '') {
$message .= $buf_o . $soft_break;
$buf = $word;
}
}
$firstword = false;
}
$message .= $buf . self::CRLF;
}
return $message;
}
/**
* Find the last character boundary prior to $maxLength in a utf-8
* quoted-printable encoded string.
* Original written by Colin Brown.
* @access public
* @param string $encodedText utf-8 QP text
* @param integer $maxLength Find the last character boundary prior to this length
* @return integer
*/
public function utf8CharBoundary($encodedText, $maxLength)
{
$foundSplitPos = false;
$lookBack = 3;
while (!$foundSplitPos) {
$lastChunk = substr($encodedText, $maxLength - $lookBack, $lookBack);
$encodedCharPos = strpos($lastChunk, '=');
if (false !== $encodedCharPos) {
// Found start of encoded character byte within $lookBack block.
// Check the encoded byte value (the 2 chars after the '=')
$hex = substr($encodedText, $maxLength - $lookBack + $encodedCharPos + 1, 2);
$dec = hexdec($hex);
if ($dec < 128) {
// Single byte character.
// If the encoded char was found at pos 0, it will fit
// otherwise reduce maxLength to start of the encoded char
if ($encodedCharPos > 0) {
$maxLength = $maxLength - ($lookBack - $encodedCharPos);
}
$foundSplitPos = true;
} elseif ($dec >= 192) {
// First byte of a multi byte character
// Reduce maxLength to split at start of character
$maxLength = $maxLength - ($lookBack - $encodedCharPos);
$foundSplitPos = true;
} elseif ($dec < 192) {
// Middle byte of a multi byte character, look further back
$lookBack += 3;
}
} else {
// No encoded character found
$foundSplitPos = true;
}
}
return $maxLength;
}
/**
* Apply word wrapping to the message body.
* Wraps the message body to the number of chars set in the WordWrap property.
* You should only do this to plain-text bodies as wrapping HTML tags may break them.
* This is called automatically by createBody(), so you don't need to call it yourself.
* @access public
* @return void
*/
public function setWordWrap()
{
if ($this->WordWrap < 1) {
return;
}
switch ($this->message_type) {
case 'alt':
case 'alt_inline':
case 'alt_attach':
case 'alt_inline_attach':
$this->AltBody = $this->wrapText($this->AltBody, $this->WordWrap);
break;
default:
$this->Body = $this->wrapText($this->Body, $this->WordWrap);
break;
}
}
/**
* Assemble message headers.
* @access public
* @return string The assembled headers
*/
public function createHeader()
{
$result = '';
$result .= $this->headerLine('Date', $this->MessageDate == '' ? self::rfcDate() : $this->MessageDate);
// To be created automatically by mail()
if ($this->SingleTo) {
if ($this->Mailer != 'mail') {
foreach ($this->to as $toaddr) {
$this->SingleToArray[] = $this->addrFormat($toaddr);
}
}
} else {
if (count($this->to) > 0) {
if ($this->Mailer != 'mail') {
$result .= $this->addrAppend('To', $this->to);
}
} elseif (count($this->cc) == 0) {
$result .= $this->headerLine('To', 'undisclosed-recipients:;');
}
}
$result .= $this->addrAppend('From', array(array(trim($this->From), $this->FromName)));
// sendmail and mail() extract Cc from the header before sending
if (count($this->cc) > 0) {
$result .= $this->addrAppend('Cc', $this->cc);
}
// sendmail and mail() extract Bcc from the header before sending
if ((
$this->Mailer == 'sendmail' or $this->Mailer == 'qmail' or $this->Mailer == 'mail'
)
and count($this->bcc) > 0
) {
$result .= $this->addrAppend('Bcc', $this->bcc);
}
if (count($this->ReplyTo) > 0) {
$result .= $this->addrAppend('Reply-To', $this->ReplyTo);
}
// mail() sets the subject itself
if ($this->Mailer != 'mail') {
$result .= $this->headerLine('Subject', $this->encodeHeader($this->secureHeader($this->Subject)));
}
// Only allow a custom message ID if it conforms to RFC 5322 section 3.6.4
// https://tools.ietf.org/html/rfc5322#section-3.6.4
if ('' != $this->MessageID and preg_match('/^<.*@.*>$/', $this->MessageID)) {
$this->lastMessageID = $this->MessageID;
} else {
$this->lastMessageID = sprintf('<%s@%s>', $this->uniqueid, $this->serverHostname());
}
$result .= $this->headerLine('Message-ID', $this->lastMessageID);
if (!is_null($this->Priority)) {
$result .= $this->headerLine('X-Priority', $this->Priority);
}
if ($this->XMailer == '') {
$result .= $this->headerLine(
'X-Mailer',
'PHPMailer ' . $this->Version . ' (https://github.com/PHPMailer/PHPMailer)'
);
} else {
$myXmailer = trim($this->XMailer);
if ($myXmailer) {
$result .= $this->headerLine('X-Mailer', $myXmailer);
}
}
if ($this->ConfirmReadingTo != '') {
$result .= $this->headerLine('Disposition-Notification-To', '<' . $this->ConfirmReadingTo . '>');
}
// Add custom headers
foreach ($this->CustomHeader as $header) {
$result .= $this->headerLine(
trim($header[0]),
$this->encodeHeader(trim($header[1]))
);
}
if (!$this->sign_key_file) {
$result .= $this->headerLine('MIME-Version', '1.0');
$result .= $this->getMailMIME();
}
return $result;
}
/**
* Get the message MIME type headers.
* @access public
* @return string
*/
public function getMailMIME()
{
$result = '';
$ismultipart = true;
switch ($this->message_type) {
case 'inline':
$result .= $this->headerLine('Content-Type', 'multipart/related;');
$result .= $this->textLine("\tboundary=\"" . $this->boundary[1] . '"');
break;
case 'attach':
case 'inline_attach':
case 'alt_attach':
case 'alt_inline_attach':
$result .= $this->headerLine('Content-Type', 'multipart/mixed;');
$result .= $this->textLine("\tboundary=\"" . $this->boundary[1] . '"');
break;
case 'alt':
case 'alt_inline':
$result .= $this->headerLine('Content-Type', 'multipart/alternative;');
$result .= $this->textLine("\tboundary=\"" . $this->boundary[1] . '"');
break;
default:
// Catches case 'plain': and case '':
$result .= $this->textLine('Content-Type: ' . $this->ContentType . '; charset=' . $this->CharSet);
$ismultipart = false;
break;
}
// RFC1341 part 5 says 7bit is assumed if not specified
if ($this->Encoding != '7bit') {
// RFC 2045 section 6.4 says multipart MIME parts may only use 7bit, 8bit or binary CTE
if ($ismultipart) {
if ($this->Encoding == '8bit') {
$result .= $this->headerLine('Content-Transfer-Encoding', '8bit');
}
// The only remaining alternatives are quoted-printable and base64, which are both 7bit compatible
} else {
$result .= $this->headerLine('Content-Transfer-Encoding', $this->Encoding);
}
}
if ($this->Mailer != 'mail') {
$result .= $this->LE;
}
return $result;
}
/**
* Returns the whole MIME message.
* Includes complete headers and body.
* Only valid post preSend().
* @see PHPMailer::preSend()
* @access public
* @return string
*/
public function getSentMIMEMessage()
{
return rtrim($this->MIMEHeader . $this->mailHeader, "\n\r") . self::CRLF . self::CRLF . $this->MIMEBody;
}
/**
* Create unique ID
* @return string
*/
protected function generateId() {
return md5(uniqid(time()));
}
/**
* Assemble the message body.
* Returns an empty string on failure.
* @access public
* @throws phpmailerException
* @return string The assembled message body
*/
public function createBody()
{
$body = '';
//Create unique IDs and preset boundaries
$this->uniqueid = $this->generateId();
$this->boundary[1] = 'b1_' . $this->uniqueid;
$this->boundary[2] = 'b2_' . $this->uniqueid;
$this->boundary[3] = 'b3_' . $this->uniqueid;
if ($this->sign_key_file) {
$body .= $this->getMailMIME() . $this->LE;
}
$this->setWordWrap();
$bodyEncoding = $this->Encoding;
$bodyCharSet = $this->CharSet;
//Can we do a 7-bit downgrade?
if ($bodyEncoding == '8bit' and !$this->has8bitChars($this->Body)) {
$bodyEncoding = '7bit';
//All ISO 8859, Windows codepage and UTF-8 charsets are ascii compatible up to 7-bit
$bodyCharSet = 'us-ascii';
}
//If lines are too long, and we're not already using an encoding that will shorten them,
//change to quoted-printable transfer encoding for the body part only
if ('base64' != $this->Encoding and self::hasLineLongerThanMax($this->Body)) {
$bodyEncoding = 'quoted-printable';
}
$altBodyEncoding = $this->Encoding;
$altBodyCharSet = $this->CharSet;
//Can we do a 7-bit downgrade?
if ($altBodyEncoding == '8bit' and !$this->has8bitChars($this->AltBody)) {
$altBodyEncoding = '7bit';
//All ISO 8859, Windows codepage and UTF-8 charsets are ascii compatible up to 7-bit
$altBodyCharSet = 'us-ascii';
}
//If lines are too long, and we're not already using an encoding that will shorten them,
//change to quoted-printable transfer encoding for the alt body part only
if ('base64' != $altBodyEncoding and self::hasLineLongerThanMax($this->AltBody)) {
$altBodyEncoding = 'quoted-printable';
}
//Use this as a preamble in all multipart message types
$mimepre = "This is a multi-part message in MIME format." . $this->LE . $this->LE;
switch ($this->message_type) {
case 'inline':
$body .= $mimepre;
$body .= $this->getBoundary($this->boundary[1], $bodyCharSet, '', $bodyEncoding);
$body .= $this->encodeString($this->Body, $bodyEncoding);
$body .= $this->LE . $this->LE;
$body .= $this->attachAll('inline', $this->boundary[1]);
break;
case 'attach':
$body .= $mimepre;
$body .= $this->getBoundary($this->boundary[1], $bodyCharSet, '', $bodyEncoding);
$body .= $this->encodeString($this->Body, $bodyEncoding);
$body .= $this->LE . $this->LE;
$body .= $this->attachAll('attachment', $this->boundary[1]);
break;
case 'inline_attach':
$body .= $mimepre;
$body .= $this->textLine('--' . $this->boundary[1]);
$body .= $this->headerLine('Content-Type', 'multipart/related;');
$body .= $this->textLine("\tboundary=\"" . $this->boundary[2] . '"');
$body .= $this->LE;
$body .= $this->getBoundary($this->boundary[2], $bodyCharSet, '', $bodyEncoding);
$body .= $this->encodeString($this->Body, $bodyEncoding);
$body .= $this->LE . $this->LE;
$body .= $this->attachAll('inline', $this->boundary[2]);
$body .= $this->LE;
$body .= $this->attachAll('attachment', $this->boundary[1]);
break;
case 'alt':
$body .= $mimepre;
$body .= $this->getBoundary($this->boundary[1], $altBodyCharSet, 'text/plain', $altBodyEncoding);
$body .= $this->encodeString($this->AltBody, $altBodyEncoding);
$body .= $this->LE . $this->LE;
$body .= $this->getBoundary($this->boundary[1], $bodyCharSet, 'text/html', $bodyEncoding);
$body .= $this->encodeString($this->Body, $bodyEncoding);
$body .= $this->LE . $this->LE;
if (!empty($this->Ical)) {
$body .= $this->getBoundary($this->boundary[1], '', 'text/calendar; method=REQUEST', '');
$body .= $this->encodeString($this->Ical, $this->Encoding);
$body .= $this->LE . $this->LE;
}
$body .= $this->endBoundary($this->boundary[1]);
break;
case 'alt_inline':
$body .= $mimepre;
$body .= $this->getBoundary($this->boundary[1], $altBodyCharSet, 'text/plain', $altBodyEncoding);
$body .= $this->encodeString($this->AltBody, $altBodyEncoding);
$body .= $this->LE . $this->LE;
$body .= $this->textLine('--' . $this->boundary[1]);
$body .= $this->headerLine('Content-Type', 'multipart/related;');
$body .= $this->textLine("\tboundary=\"" . $this->boundary[2] . '"');
$body .= $this->LE;
$body .= $this->getBoundary($this->boundary[2], $bodyCharSet, 'text/html', $bodyEncoding);
$body .= $this->encodeString($this->Body, $bodyEncoding);
$body .= $this->LE . $this->LE;
$body .= $this->attachAll('inline', $this->boundary[2]);
$body .= $this->LE;
$body .= $this->endBoundary($this->boundary[1]);
break;
case 'alt_attach':
$body .= $mimepre;
$body .= $this->textLine('--' . $this->boundary[1]);
$body .= $this->headerLine('Content-Type', 'multipart/alternative;');
$body .= $this->textLine("\tboundary=\"" . $this->boundary[2] . '"');
$body .= $this->LE;
$body .= $this->getBoundary($this->boundary[2], $altBodyCharSet, 'text/plain', $altBodyEncoding);
$body .= $this->encodeString($this->AltBody, $altBodyEncoding);
$body .= $this->LE . $this->LE;
$body .= $this->getBoundary($this->boundary[2], $bodyCharSet, 'text/html', $bodyEncoding);
$body .= $this->encodeString($this->Body, $bodyEncoding);
$body .= $this->LE . $this->LE;
$body .= $this->endBoundary($this->boundary[2]);
$body .= $this->LE;
$body .= $this->attachAll('attachment', $this->boundary[1]);
break;
case 'alt_inline_attach':
$body .= $mimepre;
$body .= $this->textLine('--' . $this->boundary[1]);
$body .= $this->headerLine('Content-Type', 'multipart/alternative;');
$body .= $this->textLine("\tboundary=\"" . $this->boundary[2] . '"');
$body .= $this->LE;
$body .= $this->getBoundary($this->boundary[2], $altBodyCharSet, 'text/plain', $altBodyEncoding);
$body .= $this->encodeString($this->AltBody, $altBodyEncoding);
$body .= $this->LE . $this->LE;
$body .= $this->textLine('--' . $this->boundary[2]);
$body .= $this->headerLine('Content-Type', 'multipart/related;');
$body .= $this->textLine("\tboundary=\"" . $this->boundary[3] . '"');
$body .= $this->LE;
$body .= $this->getBoundary($this->boundary[3], $bodyCharSet, 'text/html', $bodyEncoding);
$body .= $this->encodeString($this->Body, $bodyEncoding);
$body .= $this->LE . $this->LE;
$body .= $this->attachAll('inline', $this->boundary[3]);
$body .= $this->LE;
$body .= $this->endBoundary($this->boundary[2]);
$body .= $this->LE;
$body .= $this->attachAll('attachment', $this->boundary[1]);
break;
default:
// Catch case 'plain' and case '', applies to simple `text/plain` and `text/html` body content types
//Reset the `Encoding` property in case we changed it for line length reasons
$this->Encoding = $bodyEncoding;
$body .= $this->encodeString($this->Body, $this->Encoding);
break;
}
if ($this->isError()) {
$body = '';
} elseif ($this->sign_key_file) {
try {
if (!defined('PKCS7_TEXT')) {
throw new phpmailerException($this->lang('extension_missing') . 'openssl');
}
// @TODO would be nice to use php://temp streams here, but need to wrap for PHP < 5.1
$file = tempnam(sys_get_temp_dir(), 'mail');
if (false === file_put_contents($file, $body)) {
throw new phpmailerException($this->lang('signing') . ' Could not write temp file');
}
$signed = tempnam(sys_get_temp_dir(), 'signed');
//Workaround for PHP bug https://bugs.php.net/bug.php?id=69197
if (empty($this->sign_extracerts_file)) {
$sign = @openssl_pkcs7_sign(
$file,
$signed,
'file://' . realpath($this->sign_cert_file),
array('file://' . realpath($this->sign_key_file), $this->sign_key_pass),
null
);
} else {
$sign = @openssl_pkcs7_sign(
$file,
$signed,
'file://' . realpath($this->sign_cert_file),
array('file://' . realpath($this->sign_key_file), $this->sign_key_pass),
null,
PKCS7_DETACHED,
$this->sign_extracerts_file
);
}
if ($sign) {
@unlink($file);
$body = file_get_contents($signed);
@unlink($signed);
//The message returned by openssl contains both headers and body, so need to split them up
$parts = explode("\n\n", $body, 2);
$this->MIMEHeader .= $parts[0] . $this->LE . $this->LE;
$body = $parts[1];
} else {
@unlink($file);
@unlink($signed);
throw new phpmailerException($this->lang('signing') . openssl_error_string());
}
} catch (phpmailerException $exc) {
$body = '';
if ($this->exceptions) {
throw $exc;
}
}
}
return $body;
}
/**
* Return the start of a message boundary.
* @access protected
* @param string $boundary
* @param string $charSet
* @param string $contentType
* @param string $encoding
* @return string
*/
protected function getBoundary($boundary, $charSet, $contentType, $encoding)
{
$result = '';
if ($charSet == '') {
$charSet = $this->CharSet;
}
if ($contentType == '') {
$contentType = $this->ContentType;
}
if ($encoding == '') {
$encoding = $this->Encoding;
}
$result .= $this->textLine('--' . $boundary);
$result .= sprintf('Content-Type: %s; charset=%s', $contentType, $charSet);
$result .= $this->LE;
// RFC1341 part 5 says 7bit is assumed if not specified
if ($encoding != '7bit') {
$result .= $this->headerLine('Content-Transfer-Encoding', $encoding);
}
$result .= $this->LE;
return $result;
}
/**
* Return the end of a message boundary.
* @access protected
* @param string $boundary
* @return string
*/
protected function endBoundary($boundary)
{
return $this->LE . '--' . $boundary . '--' . $this->LE;
}
/**
* Set the message type.
* PHPMailer only supports some preset message types, not arbitrary MIME structures.
* @access protected
* @return void
*/
protected function setMessageType()
{
$type = array();
if ($this->alternativeExists()) {
$type[] = 'alt';
}
if ($this->inlineImageExists()) {
$type[] = 'inline';
}
if ($this->attachmentExists()) {
$type[] = 'attach';
}
$this->message_type = implode('_', $type);
if ($this->message_type == '') {
//The 'plain' message_type refers to the message having a single body element, not that it is plain-text
$this->message_type = 'plain';
}
}
/**
* Format a header line.
* @access public
* @param string $name
* @param string $value
* @return string
*/
public function headerLine($name, $value)
{
return $name . ': ' . $value . $this->LE;
}
/**
* Return a formatted mail line.
* @access public
* @param string $value
* @return string
*/
public function textLine($value)
{
return $value . $this->LE;
}
/**
* Add an attachment from a path on the filesystem.
* Never use a user-supplied path to a file!
* Returns false if the file could not be found or read.
* @param string $path Path to the attachment.
* @param string $name Overrides the attachment name.
* @param string $encoding File encoding (see $Encoding).
* @param string $type File extension (MIME) type.
* @param string $disposition Disposition to use
* @throws phpmailerException
* @return boolean
*/
public function addAttachment($path, $name = '', $encoding = 'base64', $type = '', $disposition = 'attachment')
{
try {
if (!@is_file($path)) {
throw new phpmailerException($this->lang('file_access') . $path, self::STOP_CONTINUE);
}
// If a MIME type is not specified, try to work it out from the file name
if ($type == '') {
$type = self::filenameToType($path);
}
$filename = basename($path);
if ($name == '') {
$name = $filename;
}
$this->attachment[] = array(
0 => $path,
1 => $filename,
2 => $name,
3 => $encoding,
4 => $type,
5 => false, // isStringAttachment
6 => $disposition,
7 => 0
);
} catch (phpmailerException $exc) {
$this->setError($exc->getMessage());
$this->edebug($exc->getMessage());
if ($this->exceptions) {
throw $exc;
}
return false;
}
return true;
}
/**
* Return the array of attachments.
* @return array
*/
public function getAttachments()
{
return $this->attachment;
}
/**
* Attach all file, string, and binary attachments to the message.
* Returns an empty string on failure.
* @access protected
* @param string $disposition_type
* @param string $boundary
* @return string
*/
protected function attachAll($disposition_type, $boundary)
{
// Return text of body
$mime = array();
$cidUniq = array();
$incl = array();
// Add all attachments
foreach ($this->attachment as $attachment) {
// Check if it is a valid disposition_filter
if ($attachment[6] == $disposition_type) {
// Check for string attachment
$string = '';
$path = '';
$bString = $attachment[5];
if ($bString) {
$string = $attachment[0];
} else {
$path = $attachment[0];
}
$inclhash = md5(serialize($attachment));
if (in_array($inclhash, $incl)) {
continue;
}
$incl[] = $inclhash;
$name = $attachment[2];
$encoding = $attachment[3];
$type = $attachment[4];
$disposition = $attachment[6];
$cid = $attachment[7];
if ($disposition == 'inline' && array_key_exists($cid, $cidUniq)) {
continue;
}
$cidUniq[$cid] = true;
$mime[] = sprintf('--%s%s', $boundary, $this->LE);
//Only include a filename property if we have one
if (!empty($name)) {
$mime[] = sprintf(
'Content-Type: %s; name="%s"%s',
$type,
$this->encodeHeader($this->secureHeader($name)),
$this->LE
);
} else {
$mime[] = sprintf(
'Content-Type: %s%s',
$type,
$this->LE
);
}
// RFC1341 part 5 says 7bit is assumed if not specified
if ($encoding != '7bit') {
$mime[] = sprintf('Content-Transfer-Encoding: %s%s', $encoding, $this->LE);
}
if ($disposition == 'inline') {
$mime[] = sprintf('Content-ID: <%s>%s', $cid, $this->LE);
}
// If a filename contains any of these chars, it should be quoted,
// but not otherwise: RFC2183 & RFC2045 5.1
// Fixes a warning in IETF's msglint MIME checker
// Allow for bypassing the Content-Disposition header totally
if (!(empty($disposition))) {
$encoded_name = $this->encodeHeader($this->secureHeader($name));
if (preg_match('/[ \(\)<>@,;:\\"\/\[\]\?=]/', $encoded_name)) {
$mime[] = sprintf(
'Content-Disposition: %s; filename="%s"%s',
$disposition,
$encoded_name,
$this->LE . $this->LE
);
} else {
if (!empty($encoded_name)) {
$mime[] = sprintf(
'Content-Disposition: %s; filename=%s%s',
$disposition,
$encoded_name,
$this->LE . $this->LE
);
} else {
$mime[] = sprintf(
'Content-Disposition: %s%s',
$disposition,
$this->LE . $this->LE
);
}
}
} else {
$mime[] = $this->LE;
}
// Encode as string attachment
if ($bString) {
$mime[] = $this->encodeString($string, $encoding);
if ($this->isError()) {
return '';
}
$mime[] = $this->LE . $this->LE;
} else {
$mime[] = $this->encodeFile($path, $encoding);
if ($this->isError()) {
return '';
}
$mime[] = $this->LE . $this->LE;
}
}
}
$mime[] = sprintf('--%s--%s', $boundary, $this->LE);
return implode('', $mime);
}
/**
* Encode a file attachment in requested format.
* Returns an empty string on failure.
* @param string $path The full path to the file
* @param string $encoding The encoding to use; one of 'base64', '7bit', '8bit', 'binary', 'quoted-printable'
* @throws phpmailerException
* @access protected
* @return string
*/
protected function encodeFile($path, $encoding = 'base64')
{
try {
if (!is_readable($path)) {
throw new phpmailerException($this->lang('file_open') . $path, self::STOP_CONTINUE);
}
$magic_quotes = get_magic_quotes_runtime();
if ($magic_quotes) {
if (version_compare(PHP_VERSION, '5.3.0', '<')) {
set_magic_quotes_runtime(false);
} else {
//Doesn't exist in PHP 5.4, but we don't need to check because
//get_magic_quotes_runtime always returns false in 5.4+
//so it will never get here
ini_set('magic_quotes_runtime', false);
}
}
$file_buffer = file_get_contents($path);
$file_buffer = $this->encodeString($file_buffer, $encoding);
if ($magic_quotes) {
if (version_compare(PHP_VERSION, '5.3.0', '<')) {
set_magic_quotes_runtime($magic_quotes);
} else {
ini_set('magic_quotes_runtime', $magic_quotes);
}
}
return $file_buffer;
} catch (Exception $exc) {
$this->setError($exc->getMessage());
return '';
}
}
/**
* Encode a string in requested format.
* Returns an empty string on failure.
* @param string $str The text to encode
* @param string $encoding The encoding to use; one of 'base64', '7bit', '8bit', 'binary', 'quoted-printable'
* @access public
* @return string
*/
public function encodeString($str, $encoding = 'base64')
{
$encoded = '';
switch (strtolower($encoding)) {
case 'base64':
$encoded = chunk_split(base64_encode($str), 76, $this->LE);
break;
case '7bit':
case '8bit':
$encoded = $this->fixEOL($str);
// Make sure it ends with a line break
if (substr($encoded, -(strlen($this->LE))) != $this->LE) {
$encoded .= $this->LE;
}
break;
case 'binary':
$encoded = $str;
break;
case 'quoted-printable':
$encoded = $this->encodeQP($str);
break;
default:
$this->setError($this->lang('encoding') . $encoding);
break;
}
return $encoded;
}
/**
* Encode a header string optimally.
* Picks shortest of Q, B, quoted-printable or none.
* @access public
* @param string $str
* @param string $position
* @return string
*/
public function encodeHeader($str, $position = 'text')
{
$matchcount = 0;
switch (strtolower($position)) {
case 'phrase':
if (!preg_match('/[\200-\377]/', $str)) {
// Can't use addslashes as we don't know the value of magic_quotes_sybase
$encoded = addcslashes($str, "\0..\37\177\\\"");
if (($str == $encoded) && !preg_match('/[^A-Za-z0-9!#$%&\'*+\/=?^_`{|}~ -]/', $str)) {
return ($encoded);
} else {
return ("\"$encoded\"");
}
}
$matchcount = preg_match_all('/[^\040\041\043-\133\135-\176]/', $str, $matches);
break;
/** @noinspection PhpMissingBreakStatementInspection */
case 'comment':
$matchcount = preg_match_all('/[()"]/', $str, $matches);
// Intentional fall-through
case 'text':
default:
$matchcount += preg_match_all('/[\000-\010\013\014\016-\037\177-\377]/', $str, $matches);
break;
}
//There are no chars that need encoding
if ($matchcount == 0) {
return ($str);
}
$maxlen = 75 - 7 - strlen($this->CharSet);
// Try to select the encoding which should produce the shortest output
if ($matchcount > strlen($str) / 3) {
// More than a third of the content will need encoding, so B encoding will be most efficient
$encoding = 'B';
if (function_exists('mb_strlen') && $this->hasMultiBytes($str)) {
// Use a custom function which correctly encodes and wraps long
// multibyte strings without breaking lines within a character
$encoded = $this->base64EncodeWrapMB($str, "\n");
} else {
$encoded = base64_encode($str);
$maxlen -= $maxlen % 4;
$encoded = trim(chunk_split($encoded, $maxlen, "\n"));
}
} else {
$encoding = 'Q';
$encoded = $this->encodeQ($str, $position);
$encoded = $this->wrapText($encoded, $maxlen, true);
$encoded = str_replace('=' . self::CRLF, "\n", trim($encoded));
}
$encoded = preg_replace('/^(.*)$/m', ' =?' . $this->CharSet . "?$encoding?\\1?=", $encoded);
$encoded = trim(str_replace("\n", $this->LE, $encoded));
return $encoded;
}
/**
* Check if a string contains multi-byte characters.
* @access public
* @param string $str multi-byte text to wrap encode
* @return boolean
*/
public function hasMultiBytes($str)
{
if (function_exists('mb_strlen')) {
return (strlen($str) > mb_strlen($str, $this->CharSet));
} else { // Assume no multibytes (we can't handle without mbstring functions anyway)
return false;
}
}
/**
* Does a string contain any 8-bit chars (in any charset)?
* @param string $text
* @return boolean
*/
public function has8bitChars($text)
{
return (boolean)preg_match('/[\x80-\xFF]/', $text);
}
/**
* Encode and wrap long multibyte strings for mail headers
* without breaking lines within a character.
* Adapted from a function by paravoid
* @link http://www.php.net/manual/en/function.mb-encode-mimeheader.php#60283
* @access public
* @param string $str multi-byte text to wrap encode
* @param string $linebreak string to use as linefeed/end-of-line
* @return string
*/
public function base64EncodeWrapMB($str, $linebreak = null)
{
$start = '=?' . $this->CharSet . '?B?';
$end = '?=';
$encoded = '';
if ($linebreak === null) {
$linebreak = $this->LE;
}
$mb_length = mb_strlen($str, $this->CharSet);
// Each line must have length <= 75, including $start and $end
$length = 75 - strlen($start) - strlen($end);
// Average multi-byte ratio
$ratio = $mb_length / strlen($str);
// Base64 has a 4:3 ratio
$avgLength = floor($length * $ratio * .75);
for ($i = 0; $i < $mb_length; $i += $offset) {
$lookBack = 0;
do {
$offset = $avgLength - $lookBack;
$chunk = mb_substr($str, $i, $offset, $this->CharSet);
$chunk = base64_encode($chunk);
$lookBack++;
} while (strlen($chunk) > $length);
$encoded .= $chunk . $linebreak;
}
// Chomp the last linefeed
$encoded = substr($encoded, 0, -strlen($linebreak));
return $encoded;
}
/**
* Encode a string in quoted-printable format.
* According to RFC2045 section 6.7.
* @access public
* @param string $string The text to encode
* @param integer $line_max Number of chars allowed on a line before wrapping
* @return string
* @link http://www.php.net/manual/en/function.quoted-printable-decode.php#89417 Adapted from this comment
*/
public function encodeQP($string, $line_max = 76)
{
// Use native function if it's available (>= PHP5.3)
if (function_exists('quoted_printable_encode')) {
return quoted_printable_encode($string);
}
// Fall back to a pure PHP implementation
$string = str_replace(
array('%20', '%0D%0A.', '%0D%0A', '%'),
array(' ', "\r\n=2E", "\r\n", '='),
rawurlencode($string)
);
return preg_replace('/[^\r\n]{' . ($line_max - 3) . '}[^=\r\n]{2}/', "$0=\r\n", $string);
}
/**
* Backward compatibility wrapper for an old QP encoding function that was removed.
* @see PHPMailer::encodeQP()
* @access public
* @param string $string
* @param integer $line_max
* @param boolean $space_conv
* @return string
* @deprecated Use encodeQP instead.
*/
public function encodeQPphp(
$string,
$line_max = 76,
/** @noinspection PhpUnusedParameterInspection */ $space_conv = false
) {
return $this->encodeQP($string, $line_max);
}
/**
* Encode a string using Q encoding.
* @link http://tools.ietf.org/html/rfc2047
* @param string $str the text to encode
* @param string $position Where the text is going to be used, see the RFC for what that means
* @access public
* @return string
*/
public function encodeQ($str, $position = 'text')
{
// There should not be any EOL in the string
$pattern = '';
$encoded = str_replace(array("\r", "\n"), '', $str);
switch (strtolower($position)) {
case 'phrase':
// RFC 2047 section 5.3
$pattern = '^A-Za-z0-9!*+\/ -';
break;
/** @noinspection PhpMissingBreakStatementInspection */
case 'comment':
// RFC 2047 section 5.2
$pattern = '\(\)"';
// intentional fall-through
// for this reason we build the $pattern without including delimiters and []
case 'text':
default:
// RFC 2047 section 5.1
// Replace every high ascii, control, =, ? and _ characters
$pattern = '\000-\011\013\014\016-\037\075\077\137\177-\377' . $pattern;
break;
}
$matches = array();
if (preg_match_all("/[{$pattern}]/", $encoded, $matches)) {
// If the string contains an '=', make sure it's the first thing we replace
// so as to avoid double-encoding
$eqkey = array_search('=', $matches[0]);
if (false !== $eqkey) {
unset($matches[0][$eqkey]);
array_unshift($matches[0], '=');
}
foreach (array_unique($matches[0]) as $char) {
$encoded = str_replace($char, '=' . sprintf('%02X', ord($char)), $encoded);
}
}
// Replace every spaces to _ (more readable than =20)
return str_replace(' ', '_', $encoded);
}
/**
* Add a string or binary attachment (non-filesystem).
* This method can be used to attach ascii or binary data,
* such as a BLOB record from a database.
* @param string $string String attachment data.
* @param string $filename Name of the attachment.
* @param string $encoding File encoding (see $Encoding).
* @param string $type File extension (MIME) type.
* @param string $disposition Disposition to use
* @return void
*/
public function addStringAttachment(
$string,
$filename,
$encoding = 'base64',
$type = '',
$disposition = 'attachment'
) {
// If a MIME type is not specified, try to work it out from the file name
if ($type == '') {
$type = self::filenameToType($filename);
}
// Append to $attachment array
$this->attachment[] = array(
0 => $string,
1 => $filename,
2 => basename($filename),
3 => $encoding,
4 => $type,
5 => true, // isStringAttachment
6 => $disposition,
7 => 0
);
}
/**
* Add an embedded (inline) attachment from a file.
* This can include images, sounds, and just about any other document type.
* These differ from 'regular' attachments in that they are intended to be
* displayed inline with the message, not just attached for download.
* This is used in HTML messages that embed the images
* the HTML refers to using the $cid value.
* Never use a user-supplied path to a file!
* @param string $path Path to the attachment.
* @param string $cid Content ID of the attachment; Use this to reference
* the content when using an embedded image in HTML.
* @param string $name Overrides the attachment name.
* @param string $encoding File encoding (see $Encoding).
* @param string $type File MIME type.
* @param string $disposition Disposition to use
* @return boolean True on successfully adding an attachment
*/
public function addEmbeddedImage($path, $cid, $name = '', $encoding = 'base64', $type = '', $disposition = 'inline')
{
if (!@is_file($path)) {
$this->setError($this->lang('file_access') . $path);
return false;
}
// If a MIME type is not specified, try to work it out from the file name
if ($type == '') {
$type = self::filenameToType($path);
}
$filename = basename($path);
if ($name == '') {
$name = $filename;
}
// Append to $attachment array
$this->attachment[] = array(
0 => $path,
1 => $filename,
2 => $name,
3 => $encoding,
4 => $type,
5 => false, // isStringAttachment
6 => $disposition,
7 => $cid
);
return true;
}
/**
* Add an embedded stringified attachment.
* This can include images, sounds, and just about any other document type.
* Be sure to set the $type to an image type for images:
* JPEG images use 'image/jpeg', GIF uses 'image/gif', PNG uses 'image/png'.
* @param string $string The attachment binary data.
* @param string $cid Content ID of the attachment; Use this to reference
* the content when using an embedded image in HTML.
* @param string $name
* @param string $encoding File encoding (see $Encoding).
* @param string $type MIME type.
* @param string $disposition Disposition to use
* @return boolean True on successfully adding an attachment
*/
public function addStringEmbeddedImage(
$string,
$cid,
$name = '',
$encoding = 'base64',
$type = '',
$disposition = 'inline'
) {
// If a MIME type is not specified, try to work it out from the name
if ($type == '' and !empty($name)) {
$type = self::filenameToType($name);
}
// Append to $attachment array
$this->attachment[] = array(
0 => $string,
1 => $name,
2 => $name,
3 => $encoding,
4 => $type,
5 => true, // isStringAttachment
6 => $disposition,
7 => $cid
);
return true;
}
/**
* Check if an inline attachment is present.
* @access public
* @return boolean
*/
public function inlineImageExists()
{
foreach ($this->attachment as $attachment) {
if ($attachment[6] == 'inline') {
return true;
}
}
return false;
}
/**
* Check if an attachment (non-inline) is present.
* @return boolean
*/
public function attachmentExists()
{
foreach ($this->attachment as $attachment) {
if ($attachment[6] == 'attachment') {
return true;
}
}
return false;
}
/**
* Check if this message has an alternative body set.
* @return boolean
*/
public function alternativeExists()
{
return !empty($this->AltBody);
}
/**
* Clear queued addresses of given kind.
* @access protected
* @param string $kind 'to', 'cc', or 'bcc'
* @return void
*/
public function clearQueuedAddresses($kind)
{
$RecipientsQueue = $this->RecipientsQueue;
foreach ($RecipientsQueue as $address => $params) {
if ($params[0] == $kind) {
unset($this->RecipientsQueue[$address]);
}
}
}
/**
* Clear all To recipients.
* @return void
*/
public function clearAddresses()
{
foreach ($this->to as $to) {
unset($this->all_recipients[strtolower($to[0])]);
}
$this->to = array();
$this->clearQueuedAddresses('to');
}
/**
* Clear all CC recipients.
* @return void
*/
public function clearCCs()
{
foreach ($this->cc as $cc) {
unset($this->all_recipients[strtolower($cc[0])]);
}
$this->cc = array();
$this->clearQueuedAddresses('cc');
}
/**
* Clear all BCC recipients.
* @return void
*/
public function clearBCCs()
{
foreach ($this->bcc as $bcc) {
unset($this->all_recipients[strtolower($bcc[0])]);
}
$this->bcc = array();
$this->clearQueuedAddresses('bcc');
}
/**
* Clear all ReplyTo recipients.
* @return void
*/
public function clearReplyTos()
{
$this->ReplyTo = array();
$this->ReplyToQueue = array();
}
/**
* Clear all recipient types.
* @return void
*/
public function clearAllRecipients()
{
$this->to = array();
$this->cc = array();
$this->bcc = array();
$this->all_recipients = array();
$this->RecipientsQueue = array();
}
/**
* Clear all filesystem, string, and binary attachments.
* @return void
*/
public function clearAttachments()
{
$this->attachment = array();
}
/**
* Clear all custom headers.
* @return void
*/
public function clearCustomHeaders()
{
$this->CustomHeader = array();
}
/**
* Add an error message to the error container.
* @access protected
* @param string $msg
* @return void
*/
protected function setError($msg)
{
$this->error_count++;
if ($this->Mailer == 'smtp' and !is_null($this->smtp)) {
$lasterror = $this->smtp->getError();
if (!empty($lasterror['error'])) {
$msg .= $this->lang('smtp_error') . $lasterror['error'];
if (!empty($lasterror['detail'])) {
$msg .= ' Detail: '. $lasterror['detail'];
}
if (!empty($lasterror['smtp_code'])) {
$msg .= ' SMTP code: ' . $lasterror['smtp_code'];
}
if (!empty($lasterror['smtp_code_ex'])) {
$msg .= ' Additional SMTP info: ' . $lasterror['smtp_code_ex'];
}
}
}
$this->ErrorInfo = $msg;
}
/**
* Return an RFC 822 formatted date.
* @access public
* @return string
* @static
*/
public static function rfcDate()
{
// Set the time zone to whatever the default is to avoid 500 errors
// Will default to UTC if it's not set properly in php.ini
date_default_timezone_set(@date_default_timezone_get());
return date('D, j M Y H:i:s O');
}
/**
* Get the server hostname.
* Returns 'localhost.localdomain' if unknown.
* @access protected
* @return string
*/
protected function serverHostname()
{
$result = 'localhost.localdomain';
if (!empty($this->Hostname)) {
$result = $this->Hostname;
} elseif (isset($_SERVER) and array_key_exists('SERVER_NAME', $_SERVER) and !empty($_SERVER['SERVER_NAME'])) {
$result = $_SERVER['SERVER_NAME'];
} elseif (function_exists('gethostname') && gethostname() !== false) {
$result = gethostname();
} elseif (php_uname('n') !== false) {
$result = php_uname('n');
}
return $result;
}
/**
* Get an error message in the current language.
* @access protected
* @param string $key
* @return string
*/
protected function lang($key)
{
if (count($this->language) < 1) {
$this->setLanguage('en'); // set the default language
}
if (array_key_exists($key, $this->language)) {
if ($key == 'smtp_connect_failed') {
//Include a link to troubleshooting docs on SMTP connection failure
//this is by far the biggest cause of support questions
//but it's usually not PHPMailer's fault.
return $this->language[$key] . ' https://github.com/PHPMailer/PHPMailer/wiki/Troubleshooting';
}
return $this->language[$key];
} else {
//Return the key as a fallback
return $key;
}
}
/**
* Check if an error occurred.
* @access public
* @return boolean True if an error did occur.
*/
public function isError()
{
return ($this->error_count > 0);
}
/**
* Ensure consistent line endings in a string.
* Changes every end of line from CRLF, CR or LF to $this->LE.
* @access public
* @param string $str String to fixEOL
* @return string
*/
public function fixEOL($str)
{
// Normalise to \n
$nstr = str_replace(array("\r\n", "\r"), "\n", $str);
// Now convert LE as needed
if ($this->LE !== "\n") {
$nstr = str_replace("\n", $this->LE, $nstr);
}
return $nstr;
}
/**
* Add a custom header.
* $name value can be overloaded to contain
* both header name and value (name:value)
* @access public
* @param string $name Custom header name
* @param string $value Header value
* @return void
*/
public function addCustomHeader($name, $value = null)
{
if ($value === null) {
// Value passed in as name:value
$this->CustomHeader[] = explode(':', $name, 2);
} else {
$this->CustomHeader[] = array($name, $value);
}
}
/**
* Returns all custom headers.
* @return array
*/
public function getCustomHeaders()
{
return $this->CustomHeader;
}
/**
* Create a message body from an HTML string.
* Automatically inlines images and creates a plain-text version by converting the HTML,
* overwriting any existing values in Body and AltBody.
* Do not source $message content from user input!
* $basedir is prepended when handling relative URLs, e.g. and must not be empty
* will look for an image file in $basedir/images/a.png and convert it to inline.
* If you don't provide a $basedir, relative paths will be left untouched (and thus probably break in email)
* If you don't want to apply these transformations to your HTML, just set Body and AltBody directly.
* @access public
* @param string $message HTML message string
* @param string $basedir Absolute path to a base directory to prepend to relative paths to images
* @param boolean|callable $advanced Whether to use the internal HTML to text converter
* or your own custom converter @see PHPMailer::html2text()
* @return string $message The transformed message Body
*/
public function msgHTML($message, $basedir = '', $advanced = false)
{
preg_match_all('/(src|background)=["\'](.*)["\']/Ui', $message, $images);
if (array_key_exists(2, $images)) {
if (strlen($basedir) > 1 && substr($basedir, -1) != '/') {
// Ensure $basedir has a trailing /
$basedir .= '/';
}
foreach ($images[2] as $imgindex => $url) {
// Convert data URIs into embedded images
if (preg_match('#^data:(image[^;,]*)(;base64)?,#', $url, $match)) {
$data = substr($url, strpos($url, ','));
if ($match[2]) {
$data = base64_decode($data);
} else {
$data = rawurldecode($data);
}
$cid = md5($url) . '@phpmailer.0'; // RFC2392 S 2
if ($this->addStringEmbeddedImage($data, $cid, 'embed' . $imgindex, 'base64', $match[1])) {
$message = str_replace(
$images[0][$imgindex],
$images[1][$imgindex] . '="cid:' . $cid . '"',
$message
);
}
continue;
}
if (
// Only process relative URLs if a basedir is provided (i.e. no absolute local paths)
!empty($basedir)
// Ignore URLs containing parent dir traversal (..)
&& (strpos($url, '..') === false)
// Do not change urls that are already inline images
&& substr($url, 0, 4) !== 'cid:'
// Do not change absolute URLs, including anonymous protocol
&& !preg_match('#^[a-z][a-z0-9+.-]*:?//#i', $url)
) {
$filename = basename($url);
$directory = dirname($url);
if ($directory == '.') {
$directory = '';
}
$cid = md5($url) . '@phpmailer.0'; // RFC2392 S 2
if (strlen($directory) > 1 && substr($directory, -1) != '/') {
$directory .= '/';
}
if ($this->addEmbeddedImage(
$basedir . $directory . $filename,
$cid,
$filename,
'base64',
self::_mime_types((string)self::mb_pathinfo($filename, PATHINFO_EXTENSION))
)
) {
$message = preg_replace(
'/' . $images[1][$imgindex] . '=["\']' . preg_quote($url, '/') . '["\']/Ui',
$images[1][$imgindex] . '="cid:' . $cid . '"',
$message
);
}
}
}
}
$this->isHTML(true);
// Convert all message body line breaks to CRLF, makes quoted-printable encoding work much better
$this->Body = $this->normalizeBreaks($message);
$this->AltBody = $this->normalizeBreaks($this->html2text($message, $advanced));
if (!$this->alternativeExists()) {
$this->AltBody = 'To view this email message, open it in a program that understands HTML!' .
self::CRLF . self::CRLF;
}
return $this->Body;
}
/**
* Convert an HTML string into plain text.
* This is used by msgHTML().
* Note - older versions of this function used a bundled advanced converter
* which was been removed for license reasons in #232.
* Example usage:
*
* // Use default conversion
* $plain = $mail->html2text($html);
* // Use your own custom converter
* $plain = $mail->html2text($html, function($html) {
* $converter = new MyHtml2text($html);
* return $converter->get_text();
* });
*
* @param string $html The HTML text to convert
* @param boolean|callable $advanced Any boolean value to use the internal converter,
* or provide your own callable for custom conversion.
* @return string
*/
public function html2text($html, $advanced = false)
{
if (is_callable($advanced)) {
return call_user_func($advanced, $html);
}
return html_entity_decode(
trim(strip_tags(preg_replace('/<(head|title|style|script)[^>]*>.*?<\/\\1>/si', '', $html))),
ENT_QUOTES,
$this->CharSet
);
}
/**
* Get the MIME type for a file extension.
* @param string $ext File extension
* @access public
* @return string MIME type of file.
* @static
*/
public static function _mime_types($ext = '')
{
$mimes = array(
'xl' => 'application/excel',
'js' => 'application/javascript',
'hqx' => 'application/mac-binhex40',
'cpt' => 'application/mac-compactpro',
'bin' => 'application/macbinary',
'doc' => 'application/msword',
'word' => 'application/msword',
'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
'xltx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.template',
'potx' => 'application/vnd.openxmlformats-officedocument.presentationml.template',
'ppsx' => 'application/vnd.openxmlformats-officedocument.presentationml.slideshow',
'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
'sldx' => 'application/vnd.openxmlformats-officedocument.presentationml.slide',
'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'dotx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.template',
'xlam' => 'application/vnd.ms-excel.addin.macroEnabled.12',
'xlsb' => 'application/vnd.ms-excel.sheet.binary.macroEnabled.12',
'class' => 'application/octet-stream',
'dll' => 'application/octet-stream',
'dms' => 'application/octet-stream',
'exe' => 'application/octet-stream',
'lha' => 'application/octet-stream',
'lzh' => 'application/octet-stream',
'psd' => 'application/octet-stream',
'sea' => 'application/octet-stream',
'so' => 'application/octet-stream',
'oda' => 'application/oda',
'pdf' => 'application/pdf',
'ai' => 'application/postscript',
'eps' => 'application/postscript',
'ps' => 'application/postscript',
'smi' => 'application/smil',
'smil' => 'application/smil',
'mif' => 'application/vnd.mif',
'xls' => 'application/vnd.ms-excel',
'ppt' => 'application/vnd.ms-powerpoint',
'wbxml' => 'application/vnd.wap.wbxml',
'wmlc' => 'application/vnd.wap.wmlc',
'dcr' => 'application/x-director',
'dir' => 'application/x-director',
'dxr' => 'application/x-director',
'dvi' => 'application/x-dvi',
'gtar' => 'application/x-gtar',
'php3' => 'application/x-httpd-php',
'php4' => 'application/x-httpd-php',
'php' => 'application/x-httpd-php',
'phtml' => 'application/x-httpd-php',
'phps' => 'application/x-httpd-php-source',
'swf' => 'application/x-shockwave-flash',
'sit' => 'application/x-stuffit',
'tar' => 'application/x-tar',
'tgz' => 'application/x-tar',
'xht' => 'application/xhtml+xml',
'xhtml' => 'application/xhtml+xml',
'zip' => 'application/zip',
'mid' => 'audio/midi',
'midi' => 'audio/midi',
'mp2' => 'audio/mpeg',
'mp3' => 'audio/mpeg',
'mpga' => 'audio/mpeg',
'aif' => 'audio/x-aiff',
'aifc' => 'audio/x-aiff',
'aiff' => 'audio/x-aiff',
'ram' => 'audio/x-pn-realaudio',
'rm' => 'audio/x-pn-realaudio',
'rpm' => 'audio/x-pn-realaudio-plugin',
'ra' => 'audio/x-realaudio',
'wav' => 'audio/x-wav',
'bmp' => 'image/bmp',
'gif' => 'image/gif',
'jpeg' => 'image/jpeg',
'jpe' => 'image/jpeg',
'jpg' => 'image/jpeg',
'png' => 'image/png',
'tiff' => 'image/tiff',
'tif' => 'image/tiff',
'eml' => 'message/rfc822',
'css' => 'text/css',
'html' => 'text/html',
'htm' => 'text/html',
'shtml' => 'text/html',
'log' => 'text/plain',
'text' => 'text/plain',
'txt' => 'text/plain',
'rtx' => 'text/richtext',
'rtf' => 'text/rtf',
'vcf' => 'text/vcard',
'vcard' => 'text/vcard',
'xml' => 'text/xml',
'xsl' => 'text/xml',
'mpeg' => 'video/mpeg',
'mpe' => 'video/mpeg',
'mpg' => 'video/mpeg',
'mov' => 'video/quicktime',
'qt' => 'video/quicktime',
'rv' => 'video/vnd.rn-realvideo',
'avi' => 'video/x-msvideo',
'movie' => 'video/x-sgi-movie'
);
if (array_key_exists(strtolower($ext), $mimes)) {
return $mimes[strtolower($ext)];
}
return 'application/octet-stream';
}
/**
* Map a file name to a MIME type.
* Defaults to 'application/octet-stream', i.e.. arbitrary binary data.
* @param string $filename A file name or full path, does not need to exist as a file
* @return string
* @static
*/
public static function filenameToType($filename)
{
// In case the path is a URL, strip any query string before getting extension
$qpos = strpos($filename, '?');
if (false !== $qpos) {
$filename = substr($filename, 0, $qpos);
}
$pathinfo = self::mb_pathinfo($filename);
return self::_mime_types($pathinfo['extension']);
}
/**
* Multi-byte-safe pathinfo replacement.
* Drop-in replacement for pathinfo(), but multibyte-safe, cross-platform-safe, old-version-safe.
* Works similarly to the one in PHP >= 5.2.0
* @link http://www.php.net/manual/en/function.pathinfo.php#107461
* @param string $path A filename or path, does not need to exist as a file
* @param integer|string $options Either a PATHINFO_* constant,
* or a string name to return only the specified piece, allows 'filename' to work on PHP < 5.2
* @return string|array
* @static
*/
public static function mb_pathinfo($path, $options = null)
{
$ret = array('dirname' => '', 'basename' => '', 'extension' => '', 'filename' => '');
$pathinfo = array();
if (preg_match('%^(.*?)[\\\\/]*(([^/\\\\]*?)(\.([^\.\\\\/]+?)|))[\\\\/\.]*$%im', $path, $pathinfo)) {
if (array_key_exists(1, $pathinfo)) {
$ret['dirname'] = $pathinfo[1];
}
if (array_key_exists(2, $pathinfo)) {
$ret['basename'] = $pathinfo[2];
}
if (array_key_exists(5, $pathinfo)) {
$ret['extension'] = $pathinfo[5];
}
if (array_key_exists(3, $pathinfo)) {
$ret['filename'] = $pathinfo[3];
}
}
switch ($options) {
case PATHINFO_DIRNAME:
case 'dirname':
return $ret['dirname'];
case PATHINFO_BASENAME:
case 'basename':
return $ret['basename'];
case PATHINFO_EXTENSION:
case 'extension':
return $ret['extension'];
case PATHINFO_FILENAME:
case 'filename':
return $ret['filename'];
default:
return $ret;
}
}
/**
* Set or reset instance properties.
* You should avoid this function - it's more verbose, less efficient, more error-prone and
* harder to debug than setting properties directly.
* Usage Example:
* `$mail->set('SMTPSecure', 'tls');`
* is the same as:
* `$mail->SMTPSecure = 'tls';`
* @access public
* @param string $name The property name to set
* @param mixed $value The value to set the property to
* @return boolean
* @TODO Should this not be using the __set() magic function?
*/
public function set($name, $value = '')
{
if (property_exists($this, $name)) {
$this->$name = $value;
return true;
} else {
$this->setError($this->lang('variable_set') . $name);
return false;
}
}
/**
* Strip newlines to prevent header injection.
* @access public
* @param string $str
* @return string
*/
public function secureHeader($str)
{
return trim(str_replace(array("\r", "\n"), '', $str));
}
/**
* Normalize line breaks in a string.
* Converts UNIX LF, Mac CR and Windows CRLF line breaks into a single line break format.
* Defaults to CRLF (for message bodies) and preserves consecutive breaks.
* @param string $text
* @param string $breaktype What kind of line break to use, defaults to CRLF
* @return string
* @access public
* @static
*/
public static function normalizeBreaks($text, $breaktype = "\r\n")
{
return preg_replace('/(\r\n|\r|\n)/ms', $breaktype, $text);
}
/**
* Set the public and private key files and password for S/MIME signing.
* @access public
* @param string $cert_filename
* @param string $key_filename
* @param string $key_pass Password for private key
* @param string $extracerts_filename Optional path to chain certificate
*/
public function sign($cert_filename, $key_filename, $key_pass, $extracerts_filename = '')
{
$this->sign_cert_file = $cert_filename;
$this->sign_key_file = $key_filename;
$this->sign_key_pass = $key_pass;
$this->sign_extracerts_file = $extracerts_filename;
}
/**
* Quoted-Printable-encode a DKIM header.
* @access public
* @param string $txt
* @return string
*/
public function DKIM_QP($txt)
{
$line = '';
for ($i = 0; $i < strlen($txt); $i++) {
$ord = ord($txt[$i]);
if (((0x21 <= $ord) && ($ord <= 0x3A)) || $ord == 0x3C || ((0x3E <= $ord) && ($ord <= 0x7E))) {
$line .= $txt[$i];
} else {
$line .= '=' . sprintf('%02X', $ord);
}
}
return $line;
}
/**
* Generate a DKIM signature.
* @access public
* @param string $signHeader
* @throws phpmailerException
* @return string The DKIM signature value
*/
public function DKIM_Sign($signHeader)
{
if (!defined('PKCS7_TEXT')) {
if ($this->exceptions) {
throw new phpmailerException($this->lang('extension_missing') . 'openssl');
}
return '';
}
$privKeyStr = !empty($this->DKIM_private_string) ? $this->DKIM_private_string : file_get_contents($this->DKIM_private);
if ('' != $this->DKIM_passphrase) {
$privKey = openssl_pkey_get_private($privKeyStr, $this->DKIM_passphrase);
} else {
$privKey = openssl_pkey_get_private($privKeyStr);
}
//Workaround for missing digest algorithms in old PHP & OpenSSL versions
//@link http://stackoverflow.com/a/11117338/333340
if (version_compare(PHP_VERSION, '5.3.0') >= 0 and
in_array('sha256WithRSAEncryption', openssl_get_md_methods(true))) {
if (openssl_sign($signHeader, $signature, $privKey, 'sha256WithRSAEncryption')) {
openssl_pkey_free($privKey);
return base64_encode($signature);
}
} else {
$pinfo = openssl_pkey_get_details($privKey);
$hash = hash('sha256', $signHeader);
//'Magic' constant for SHA256 from RFC3447
//@link https://tools.ietf.org/html/rfc3447#page-43
$t = '3031300d060960864801650304020105000420' . $hash;
$pslen = $pinfo['bits'] / 8 - (strlen($t) / 2 + 3);
$eb = pack('H*', '0001' . str_repeat('FF', $pslen) . '00' . $t);
if (openssl_private_encrypt($eb, $signature, $privKey, OPENSSL_NO_PADDING)) {
openssl_pkey_free($privKey);
return base64_encode($signature);
}
}
openssl_pkey_free($privKey);
return '';
}
/**
* Generate a DKIM canonicalization header.
* @access public
* @param string $signHeader Header
* @return string
*/
public function DKIM_HeaderC($signHeader)
{
$signHeader = preg_replace('/\r\n\s+/', ' ', $signHeader);
$lines = explode("\r\n", $signHeader);
foreach ($lines as $key => $line) {
list($heading, $value) = explode(':', $line, 2);
$heading = strtolower($heading);
$value = preg_replace('/\s{2,}/', ' ', $value); // Compress useless spaces
$lines[$key] = $heading . ':' . trim($value); // Don't forget to remove WSP around the value
}
$signHeader = implode("\r\n", $lines);
return $signHeader;
}
/**
* Generate a DKIM canonicalization body.
* @access public
* @param string $body Message Body
* @return string
*/
public function DKIM_BodyC($body)
{
if ($body == '') {
return "\r\n";
}
// stabilize line endings
$body = str_replace("\r\n", "\n", $body);
$body = str_replace("\n", "\r\n", $body);
// END stabilize line endings
while (substr($body, strlen($body) - 4, 4) == "\r\n\r\n") {
$body = substr($body, 0, strlen($body) - 2);
}
return $body;
}
/**
* Create the DKIM header and body in a new message header.
* @access public
* @param string $headers_line Header lines
* @param string $subject Subject
* @param string $body Body
* @return string
*/
public function DKIM_Add($headers_line, $subject, $body)
{
$DKIMsignatureType = 'rsa-sha256'; // Signature & hash algorithms
$DKIMcanonicalization = 'relaxed/simple'; // Canonicalization of header/body
$DKIMquery = 'dns/txt'; // Query method
$DKIMtime = time(); // Signature Timestamp = seconds since 00:00:00 - Jan 1, 1970 (UTC time zone)
$subject_header = "Subject: $subject";
$headers = explode($this->LE, $headers_line);
$from_header = '';
$to_header = '';
$date_header = '';
$current = '';
foreach ($headers as $header) {
if (strpos($header, 'From:') === 0) {
$from_header = $header;
$current = 'from_header';
} elseif (strpos($header, 'To:') === 0) {
$to_header = $header;
$current = 'to_header';
} elseif (strpos($header, 'Date:') === 0) {
$date_header = $header;
$current = 'date_header';
} else {
if (!empty($$current) && strpos($header, ' =?') === 0) {
$$current .= $header;
} else {
$current = '';
}
}
}
$from = str_replace('|', '=7C', $this->DKIM_QP($from_header));
$to = str_replace('|', '=7C', $this->DKIM_QP($to_header));
$date = str_replace('|', '=7C', $this->DKIM_QP($date_header));
$subject = str_replace(
'|',
'=7C',
$this->DKIM_QP($subject_header)
); // Copied header fields (dkim-quoted-printable)
$body = $this->DKIM_BodyC($body);
$DKIMlen = strlen($body); // Length of body
$DKIMb64 = base64_encode(pack('H*', hash('sha256', $body))); // Base64 of packed binary SHA-256 hash of body
if ('' == $this->DKIM_identity) {
$ident = '';
} else {
$ident = ' i=' . $this->DKIM_identity . ';';
}
$dkimhdrs = 'DKIM-Signature: v=1; a=' .
$DKIMsignatureType . '; q=' .
$DKIMquery . '; l=' .
$DKIMlen . '; s=' .
$this->DKIM_selector .
";\r\n" .
"\tt=" . $DKIMtime . '; c=' . $DKIMcanonicalization . ";\r\n" .
"\th=From:To:Date:Subject;\r\n" .
"\td=" . $this->DKIM_domain . ';' . $ident . "\r\n" .
"\tz=$from\r\n" .
"\t|$to\r\n" .
"\t|$date\r\n" .
"\t|$subject;\r\n" .
"\tbh=" . $DKIMb64 . ";\r\n" .
"\tb=";
$toSign = $this->DKIM_HeaderC(
$from_header . "\r\n" .
$to_header . "\r\n" .
$date_header . "\r\n" .
$subject_header . "\r\n" .
$dkimhdrs
);
$signed = $this->DKIM_Sign($toSign);
return $dkimhdrs . $signed . "\r\n";
}
/**
* Detect if a string contains a line longer than the maximum line length allowed.
* @param string $str
* @return boolean
* @static
*/
public static function hasLineLongerThanMax($str)
{
//+2 to include CRLF line break for a 1000 total
return (boolean)preg_match('/^(.{'.(self::MAX_LINE_LENGTH + 2).',})/m', $str);
}
/**
* Allows for public read access to 'to' property.
* @note: Before the send() call, queued addresses (i.e. with IDN) are not yet included.
* @access public
* @return array
*/
public function getToAddresses()
{
return $this->to;
}
/**
* Allows for public read access to 'cc' property.
* @note: Before the send() call, queued addresses (i.e. with IDN) are not yet included.
* @access public
* @return array
*/
public function getCcAddresses()
{
return $this->cc;
}
/**
* Allows for public read access to 'bcc' property.
* @note: Before the send() call, queued addresses (i.e. with IDN) are not yet included.
* @access public
* @return array
*/
public function getBccAddresses()
{
return $this->bcc;
}
/**
* Allows for public read access to 'ReplyTo' property.
* @note: Before the send() call, queued addresses (i.e. with IDN) are not yet included.
* @access public
* @return array
*/
public function getReplyToAddresses()
{
return $this->ReplyTo;
}
/**
* Allows for public read access to 'all_recipients' property.
* @note: Before the send() call, queued addresses (i.e. with IDN) are not yet included.
* @access public
* @return array
*/
public function getAllRecipientAddresses()
{
return $this->all_recipients;
}
/**
* Perform a callback.
* @param boolean $isSent
* @param array $to
* @param array $cc
* @param array $bcc
* @param string $subject
* @param string $body
* @param string $from
*/
protected function doCallback($isSent, $to, $cc, $bcc, $subject, $body, $from)
{
if (!empty($this->action_function) && is_callable($this->action_function)) {
$params = array($isSent, $to, $cc, $bcc, $subject, $body, $from);
call_user_func_array($this->action_function, $params);
}
}
}
/**
* PHPMailer exception handler
* @package PHPMailer
*/
class phpmailerException extends Exception
{
/**
* Prettify error message output
* @return string
*/
public function errorMessage()
{
$errorMsg = '' . htmlspecialchars($this->getMessage()) . " \n";
return $errorMsg;
}
}
================================================
FILE: lib/phpmailer/class.pop3.php
================================================
* @author Jim Jagielski (jimjag)
* @author Andy Prevost (codeworxtech)
* @author Brent R. Matzelle (original founder)
* @copyright 2012 - 2014 Marcus Bointon
* @copyright 2010 - 2012 Jim Jagielski
* @copyright 2004 - 2009 Andy Prevost
* @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
* @note This program is distributed in the hope that it will be useful - WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE.
*/
/**
* PHPMailer POP-Before-SMTP Authentication Class.
* Specifically for PHPMailer to use for RFC1939 POP-before-SMTP authentication.
* Does not support APOP.
* @package PHPMailer
* @author Richard Davey (original author)
* @author Marcus Bointon (Synchro/coolbru)
* @author Jim Jagielski (jimjag)
* @author Andy Prevost (codeworxtech)
*/
class POP3
{
/**
* The POP3 PHPMailer Version number.
* @var string
* @access public
*/
public $Version = '5.2.26';
/**
* Default POP3 port number.
* @var integer
* @access public
*/
public $POP3_PORT = 110;
/**
* Default timeout in seconds.
* @var integer
* @access public
*/
public $POP3_TIMEOUT = 30;
/**
* POP3 Carriage Return + Line Feed.
* @var string
* @access public
* @deprecated Use the constant instead
*/
public $CRLF = "\r\n";
/**
* Debug display level.
* Options: 0 = no, 1+ = yes
* @var integer
* @access public
*/
public $do_debug = 0;
/**
* POP3 mail server hostname.
* @var string
* @access public
*/
public $host;
/**
* POP3 port number.
* @var integer
* @access public
*/
public $port;
/**
* POP3 Timeout Value in seconds.
* @var integer
* @access public
*/
public $tval;
/**
* POP3 username
* @var string
* @access public
*/
public $username;
/**
* POP3 password.
* @var string
* @access public
*/
public $password;
/**
* Resource handle for the POP3 connection socket.
* @var resource
* @access protected
*/
protected $pop_conn;
/**
* Are we connected?
* @var boolean
* @access protected
*/
protected $connected = false;
/**
* Error container.
* @var array
* @access protected
*/
protected $errors = array();
/**
* Line break constant
*/
const CRLF = "\r\n";
/**
* Simple static wrapper for all-in-one POP before SMTP
* @param $host
* @param integer|boolean $port The port number to connect to
* @param integer|boolean $timeout The timeout value
* @param string $username
* @param string $password
* @param integer $debug_level
* @return boolean
*/
public static function popBeforeSmtp(
$host,
$port = false,
$timeout = false,
$username = '',
$password = '',
$debug_level = 0
) {
$pop = new POP3;
return $pop->authorise($host, $port, $timeout, $username, $password, $debug_level);
}
/**
* Authenticate with a POP3 server.
* A connect, login, disconnect sequence
* appropriate for POP-before SMTP authorisation.
* @access public
* @param string $host The hostname to connect to
* @param integer|boolean $port The port number to connect to
* @param integer|boolean $timeout The timeout value
* @param string $username
* @param string $password
* @param integer $debug_level
* @return boolean
*/
public function authorise($host, $port = false, $timeout = false, $username = '', $password = '', $debug_level = 0)
{
$this->host = $host;
// If no port value provided, use default
if (false === $port) {
$this->port = $this->POP3_PORT;
} else {
$this->port = (integer)$port;
}
// If no timeout value provided, use default
if (false === $timeout) {
$this->tval = $this->POP3_TIMEOUT;
} else {
$this->tval = (integer)$timeout;
}
$this->do_debug = $debug_level;
$this->username = $username;
$this->password = $password;
// Reset the error log
$this->errors = array();
// connect
$result = $this->connect($this->host, $this->port, $this->tval);
if ($result) {
$login_result = $this->login($this->username, $this->password);
if ($login_result) {
$this->disconnect();
return true;
}
}
// We need to disconnect regardless of whether the login succeeded
$this->disconnect();
return false;
}
/**
* Connect to a POP3 server.
* @access public
* @param string $host
* @param integer|boolean $port
* @param integer $tval
* @return boolean
*/
public function connect($host, $port = false, $tval = 30)
{
// Are we already connected?
if ($this->connected) {
return true;
}
//On Windows this will raise a PHP Warning error if the hostname doesn't exist.
//Rather than suppress it with @fsockopen, capture it cleanly instead
set_error_handler(array($this, 'catchWarning'));
if (false === $port) {
$port = $this->POP3_PORT;
}
// connect to the POP3 server
$this->pop_conn = fsockopen(
$host, // POP3 Host
$port, // Port #
$errno, // Error Number
$errstr, // Error Message
$tval
); // Timeout (seconds)
// Restore the error handler
restore_error_handler();
// Did we connect?
if (false === $this->pop_conn) {
// It would appear not...
$this->setError(array(
'error' => "Failed to connect to server $host on port $port",
'errno' => $errno,
'errstr' => $errstr
));
return false;
}
// Increase the stream time-out
stream_set_timeout($this->pop_conn, $tval, 0);
// Get the POP3 server response
$pop3_response = $this->getResponse();
// Check for the +OK
if ($this->checkResponse($pop3_response)) {
// The connection is established and the POP3 server is talking
$this->connected = true;
return true;
}
return false;
}
/**
* Log in to the POP3 server.
* Does not support APOP (RFC 2828, 4949).
* @access public
* @param string $username
* @param string $password
* @return boolean
*/
public function login($username = '', $password = '')
{
if (!$this->connected) {
$this->setError('Not connected to POP3 server');
}
if (empty($username)) {
$username = $this->username;
}
if (empty($password)) {
$password = $this->password;
}
// Send the Username
$this->sendString("USER $username" . self::CRLF);
$pop3_response = $this->getResponse();
if ($this->checkResponse($pop3_response)) {
// Send the Password
$this->sendString("PASS $password" . self::CRLF);
$pop3_response = $this->getResponse();
if ($this->checkResponse($pop3_response)) {
return true;
}
}
return false;
}
/**
* Disconnect from the POP3 server.
* @access public
*/
public function disconnect()
{
$this->sendString('QUIT');
//The QUIT command may cause the daemon to exit, which will kill our connection
//So ignore errors here
try {
@fclose($this->pop_conn);
} catch (Exception $e) {
//Do nothing
};
}
/**
* Get a response from the POP3 server.
* $size is the maximum number of bytes to retrieve
* @param integer $size
* @return string
* @access protected
*/
protected function getResponse($size = 128)
{
$response = fgets($this->pop_conn, $size);
if ($this->do_debug >= 1) {
echo "Server -> Client: $response";
}
return $response;
}
/**
* Send raw data to the POP3 server.
* @param string $string
* @return integer
* @access protected
*/
protected function sendString($string)
{
if ($this->pop_conn) {
if ($this->do_debug >= 2) { //Show client messages when debug >= 2
echo "Client -> Server: $string";
}
return fwrite($this->pop_conn, $string, strlen($string));
}
return 0;
}
/**
* Checks the POP3 server response.
* Looks for for +OK or -ERR.
* @param string $string
* @return boolean
* @access protected
*/
protected function checkResponse($string)
{
if (substr($string, 0, 3) !== '+OK') {
$this->setError(array(
'error' => "Server reported an error: $string",
'errno' => 0,
'errstr' => ''
));
return false;
} else {
return true;
}
}
/**
* Add an error to the internal error store.
* Also display debug output if it's enabled.
* @param $error
* @access protected
*/
protected function setError($error)
{
$this->errors[] = $error;
if ($this->do_debug >= 1) {
echo '
';
foreach ($this->errors as $error) {
print_r($error);
}
echo '
';
}
}
/**
* Get an array of error messages, if any.
* @return array
*/
public function getErrors()
{
return $this->errors;
}
/**
* POP3 connection error handler.
* @param integer $errno
* @param string $errstr
* @param string $errfile
* @param integer $errline
* @access protected
*/
protected function catchWarning($errno, $errstr, $errfile, $errline)
{
$this->setError(array(
'error' => "Connecting to the POP3 server raised a PHP warning: ",
'errno' => $errno,
'errstr' => $errstr,
'errfile' => $errfile,
'errline' => $errline
));
}
}
================================================
FILE: lib/phpmailer/class.smtp.php
================================================
* @author Jim Jagielski (jimjag)
* @author Andy Prevost (codeworxtech)
* @author Brent R. Matzelle (original founder)
* @copyright 2014 Marcus Bointon
* @copyright 2010 - 2012 Jim Jagielski
* @copyright 2004 - 2009 Andy Prevost
* @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
* @note This program is distributed in the hope that it will be useful - WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE.
*/
/**
* PHPMailer RFC821 SMTP email transport class.
* Implements RFC 821 SMTP commands and provides some utility methods for sending mail to an SMTP server.
* @package PHPMailer
* @author Chris Ryan
* @author Marcus Bointon
*/
class SMTP
{
/**
* The PHPMailer SMTP version number.
* @var string
*/
const VERSION = '5.2.26';
/**
* SMTP line break constant.
* @var string
*/
const CRLF = "\r\n";
/**
* The SMTP port to use if one is not specified.
* @var integer
*/
const DEFAULT_SMTP_PORT = 25;
/**
* The maximum line length allowed by RFC 2822 section 2.1.1
* @var integer
*/
const MAX_LINE_LENGTH = 998;
/**
* Debug level for no output
*/
const DEBUG_OFF = 0;
/**
* Debug level to show client -> server messages
*/
const DEBUG_CLIENT = 1;
/**
* Debug level to show client -> server and server -> client messages
*/
const DEBUG_SERVER = 2;
/**
* Debug level to show connection status, client -> server and server -> client messages
*/
const DEBUG_CONNECTION = 3;
/**
* Debug level to show all messages
*/
const DEBUG_LOWLEVEL = 4;
/**
* The PHPMailer SMTP Version number.
* @var string
* @deprecated Use the `VERSION` constant instead
* @see SMTP::VERSION
*/
public $Version = '5.2.26';
/**
* SMTP server port number.
* @var integer
* @deprecated This is only ever used as a default value, so use the `DEFAULT_SMTP_PORT` constant instead
* @see SMTP::DEFAULT_SMTP_PORT
*/
public $SMTP_PORT = 25;
/**
* SMTP reply line ending.
* @var string
* @deprecated Use the `CRLF` constant instead
* @see SMTP::CRLF
*/
public $CRLF = "\r\n";
/**
* Debug output level.
* Options:
* * self::DEBUG_OFF (`0`) No debug output, default
* * self::DEBUG_CLIENT (`1`) Client commands
* * self::DEBUG_SERVER (`2`) Client commands and server responses
* * self::DEBUG_CONNECTION (`3`) As DEBUG_SERVER plus connection status
* * self::DEBUG_LOWLEVEL (`4`) Low-level data output, all messages
* @var integer
*/
public $do_debug = self::DEBUG_OFF;
/**
* How to handle debug output.
* Options:
* * `echo` Output plain-text as-is, appropriate for CLI
* * `html` Output escaped, line breaks converted to ` `, appropriate for browser output
* * `error_log` Output to error log as configured in php.ini
*
* Alternatively, you can provide a callable expecting two params: a message string and the debug level:
*
* $smtp->Debugoutput = function($str, $level) {echo "debug level $level; message: $str";};
*
* @var string|callable
*/
public $Debugoutput = 'echo';
/**
* Whether to use VERP.
* @link http://en.wikipedia.org/wiki/Variable_envelope_return_path
* @link http://www.postfix.org/VERP_README.html Info on VERP
* @var boolean
*/
public $do_verp = false;
/**
* The timeout value for connection, in seconds.
* Default of 5 minutes (300sec) is from RFC2821 section 4.5.3.2
* This needs to be quite high to function correctly with hosts using greetdelay as an anti-spam measure.
* @link http://tools.ietf.org/html/rfc2821#section-4.5.3.2
* @var integer
*/
public $Timeout = 300;
/**
* How long to wait for commands to complete, in seconds.
* Default of 5 minutes (300sec) is from RFC2821 section 4.5.3.2
* @var integer
*/
public $Timelimit = 300;
/**
* @var array Patterns to extract an SMTP transaction id from reply to a DATA command.
* The first capture group in each regex will be used as the ID.
*/
protected $smtp_transaction_id_patterns = array(
'exim' => '/[0-9]{3} OK id=(.*)/',
'sendmail' => '/[0-9]{3} 2.0.0 (.*) Message/',
'postfix' => '/[0-9]{3} 2.0.0 Ok: queued as (.*)/'
);
/**
* @var string The last transaction ID issued in response to a DATA command,
* if one was detected
*/
protected $last_smtp_transaction_id;
/**
* The socket for the server connection.
* @var resource
*/
protected $smtp_conn;
/**
* Error information, if any, for the last SMTP command.
* @var array
*/
protected $error = array(
'error' => '',
'detail' => '',
'smtp_code' => '',
'smtp_code_ex' => ''
);
/**
* The reply the server sent to us for HELO.
* If null, no HELO string has yet been received.
* @var string|null
*/
protected $helo_rply = null;
/**
* The set of SMTP extensions sent in reply to EHLO command.
* Indexes of the array are extension names.
* Value at index 'HELO' or 'EHLO' (according to command that was sent)
* represents the server name. In case of HELO it is the only element of the array.
* Other values can be boolean TRUE or an array containing extension options.
* If null, no HELO/EHLO string has yet been received.
* @var array|null
*/
protected $server_caps = null;
/**
* The most recent reply received from the server.
* @var string
*/
protected $last_reply = '';
/**
* Output debugging info via a user-selected method.
* @see SMTP::$Debugoutput
* @see SMTP::$do_debug
* @param string $str Debug string to output
* @param integer $level The debug level of this message; see DEBUG_* constants
* @return void
*/
protected function edebug($str, $level = 0)
{
if ($level > $this->do_debug) {
return;
}
//Avoid clash with built-in function names
if (!in_array($this->Debugoutput, array('error_log', 'html', 'echo')) and is_callable($this->Debugoutput)) {
call_user_func($this->Debugoutput, $str, $level);
return;
}
switch ($this->Debugoutput) {
case 'error_log':
//Don't output, just log
error_log($str);
break;
case 'html':
//Cleans up output a bit for a better looking, HTML-safe output
echo gmdate('Y-m-d H:i:s') . ' ' . htmlentities(
preg_replace('/[\r\n]+/', '', $str),
ENT_QUOTES,
'UTF-8'
) . " \n";
break;
case 'echo':
default:
//Normalize line breaks
$str = preg_replace('/(\r\n|\r|\n)/ms', "\n", $str);
echo gmdate('Y-m-d H:i:s') . "\t" . str_replace(
"\n",
"\n \t ",
trim($str)
) . "\n";
}
}
/**
* Connect to an SMTP server.
* @param string $host SMTP server IP or host name
* @param integer $port The port number to connect to
* @param integer $timeout How long to wait for the connection to open
* @param array $options An array of options for stream_context_create()
* @access public
* @return boolean
*/
public function connect($host, $port = null, $timeout = 30, $options = array())
{
static $streamok;
//This is enabled by default since 5.0.0 but some providers disable it
//Check this once and cache the result
if (is_null($streamok)) {
$streamok = function_exists('stream_socket_client');
}
// Clear errors to avoid confusion
$this->setError('');
// Make sure we are __not__ connected
if ($this->connected()) {
// Already connected, generate error
$this->setError('Already connected to a server');
return false;
}
if (empty($port)) {
$port = self::DEFAULT_SMTP_PORT;
}
// Connect to the SMTP server
$this->edebug(
"Connection: opening to $host:$port, timeout=$timeout, options=" .
var_export($options, true),
self::DEBUG_CONNECTION
);
$errno = 0;
$errstr = '';
if ($streamok) {
$socket_context = stream_context_create($options);
set_error_handler(array($this, 'errorHandler'));
$this->smtp_conn = stream_socket_client(
$host . ":" . $port,
$errno,
$errstr,
$timeout,
STREAM_CLIENT_CONNECT,
$socket_context
);
restore_error_handler();
} else {
//Fall back to fsockopen which should work in more places, but is missing some features
$this->edebug(
"Connection: stream_socket_client not available, falling back to fsockopen",
self::DEBUG_CONNECTION
);
set_error_handler(array($this, 'errorHandler'));
$this->smtp_conn = fsockopen(
$host,
$port,
$errno,
$errstr,
$timeout
);
restore_error_handler();
}
// Verify we connected properly
if (!is_resource($this->smtp_conn)) {
$this->setError(
'Failed to connect to server',
$errno,
$errstr
);
$this->edebug(
'SMTP ERROR: ' . $this->error['error']
. ": $errstr ($errno)",
self::DEBUG_CLIENT
);
return false;
}
$this->edebug('Connection: opened', self::DEBUG_CONNECTION);
// SMTP server can take longer to respond, give longer timeout for first read
// Windows does not have support for this timeout function
if (substr(PHP_OS, 0, 3) != 'WIN') {
$max = ini_get('max_execution_time');
// Don't bother if unlimited
if ($max != 0 && $timeout > $max) {
@set_time_limit($timeout);
}
stream_set_timeout($this->smtp_conn, $timeout, 0);
}
// Get any announcement
$announce = $this->get_lines();
$this->edebug('SERVER -> CLIENT: ' . $announce, self::DEBUG_SERVER);
return true;
}
/**
* Initiate a TLS (encrypted) session.
* @access public
* @return boolean
*/
public function startTLS()
{
if (!$this->sendCommand('STARTTLS', 'STARTTLS', 220)) {
return false;
}
//Allow the best TLS version(s) we can
$crypto_method = STREAM_CRYPTO_METHOD_TLS_CLIENT;
//PHP 5.6.7 dropped inclusion of TLS 1.1 and 1.2 in STREAM_CRYPTO_METHOD_TLS_CLIENT
//so add them back in manually if we can
if (defined('STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT')) {
$crypto_method |= STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT;
$crypto_method |= STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT;
}
// Begin encrypted connection
set_error_handler(array($this, 'errorHandler'));
$crypto_ok = stream_socket_enable_crypto(
$this->smtp_conn,
true,
$crypto_method
);
restore_error_handler();
return $crypto_ok;
}
/**
* Perform SMTP authentication.
* Must be run after hello().
* @see hello()
* @param string $username The user name
* @param string $password The password
* @param string $authtype The auth type (PLAIN, LOGIN, NTLM, CRAM-MD5, XOAUTH2)
* @param string $realm The auth realm for NTLM
* @param string $workstation The auth workstation for NTLM
* @param null|OAuth $OAuth An optional OAuth instance (@see PHPMailerOAuth)
* @return bool True if successfully authenticated.* @access public
*/
public function authenticate(
$username,
$password,
$authtype = null,
$realm = '',
$workstation = '',
$OAuth = null
) {
if (!$this->server_caps) {
$this->setError('Authentication is not allowed before HELO/EHLO');
return false;
}
if (array_key_exists('EHLO', $this->server_caps)) {
// SMTP extensions are available; try to find a proper authentication method
if (!array_key_exists('AUTH', $this->server_caps)) {
$this->setError('Authentication is not allowed at this stage');
// 'at this stage' means that auth may be allowed after the stage changes
// e.g. after STARTTLS
return false;
}
self::edebug('Auth method requested: ' . ($authtype ? $authtype : 'UNKNOWN'), self::DEBUG_LOWLEVEL);
self::edebug(
'Auth methods available on the server: ' . implode(',', $this->server_caps['AUTH']),
self::DEBUG_LOWLEVEL
);
if (empty($authtype)) {
foreach (array('CRAM-MD5', 'LOGIN', 'PLAIN', 'NTLM', 'XOAUTH2') as $method) {
if (in_array($method, $this->server_caps['AUTH'])) {
$authtype = $method;
break;
}
}
if (empty($authtype)) {
$this->setError('No supported authentication methods found');
return false;
}
self::edebug('Auth method selected: ' . $authtype, self::DEBUG_LOWLEVEL);
}
if (!in_array($authtype, $this->server_caps['AUTH'])) {
$this->setError("The requested authentication method \"$authtype\" is not supported by the server");
return false;
}
} elseif (empty($authtype)) {
$authtype = 'LOGIN';
}
switch ($authtype) {
case 'PLAIN':
// Start authentication
if (!$this->sendCommand('AUTH', 'AUTH PLAIN', 334)) {
return false;
}
// Send encoded username and password
if (!$this->sendCommand(
'User & Password',
base64_encode("\0" . $username . "\0" . $password),
235
)
) {
return false;
}
break;
case 'LOGIN':
// Start authentication
if (!$this->sendCommand('AUTH', 'AUTH LOGIN', 334)) {
return false;
}
if (!$this->sendCommand("Username", base64_encode($username), 334)) {
return false;
}
if (!$this->sendCommand("Password", base64_encode($password), 235)) {
return false;
}
break;
case 'XOAUTH2':
//If the OAuth Instance is not set. Can be a case when PHPMailer is used
//instead of PHPMailerOAuth
if (is_null($OAuth)) {
return false;
}
$oauth = $OAuth->getOauth64();
// Start authentication
if (!$this->sendCommand('AUTH', 'AUTH XOAUTH2 ' . $oauth, 235)) {
return false;
}
break;
case 'NTLM':
/*
* ntlm_sasl_client.php
* Bundled with Permission
*
* How to telnet in windows:
* http://technet.microsoft.com/en-us/library/aa995718%28EXCHG.65%29.aspx
* PROTOCOL Docs http://curl.haxx.se/rfc/ntlm.html#ntlmSmtpAuthentication
*/
require_once 'extras/ntlm_sasl_client.php';
$temp = new stdClass;
$ntlm_client = new ntlm_sasl_client_class;
//Check that functions are available
if (!$ntlm_client->initialize($temp)) {
$this->setError($temp->error);
$this->edebug(
'You need to enable some modules in your php.ini file: '
. $this->error['error'],
self::DEBUG_CLIENT
);
return false;
}
//msg1
$msg1 = $ntlm_client->typeMsg1($realm, $workstation); //msg1
if (!$this->sendCommand(
'AUTH NTLM',
'AUTH NTLM ' . base64_encode($msg1),
334
)
) {
return false;
}
//Though 0 based, there is a white space after the 3 digit number
//msg2
$challenge = substr($this->last_reply, 3);
$challenge = base64_decode($challenge);
$ntlm_res = $ntlm_client->NTLMResponse(
substr($challenge, 24, 8),
$password
);
//msg3
$msg3 = $ntlm_client->typeMsg3(
$ntlm_res,
$username,
$realm,
$workstation
);
// send encoded username
return $this->sendCommand('Username', base64_encode($msg3), 235);
case 'CRAM-MD5':
// Start authentication
if (!$this->sendCommand('AUTH CRAM-MD5', 'AUTH CRAM-MD5', 334)) {
return false;
}
// Get the challenge
$challenge = base64_decode(substr($this->last_reply, 4));
// Build the response
$response = $username . ' ' . $this->hmac($challenge, $password);
// send encoded credentials
return $this->sendCommand('Username', base64_encode($response), 235);
default:
$this->setError("Authentication method \"$authtype\" is not supported");
return false;
}
return true;
}
/**
* Calculate an MD5 HMAC hash.
* Works like hash_hmac('md5', $data, $key)
* in case that function is not available
* @param string $data The data to hash
* @param string $key The key to hash with
* @access protected
* @return string
*/
protected function hmac($data, $key)
{
if (function_exists('hash_hmac')) {
return hash_hmac('md5', $data, $key);
}
// The following borrowed from
// http://php.net/manual/en/function.mhash.php#27225
// RFC 2104 HMAC implementation for php.
// Creates an md5 HMAC.
// Eliminates the need to install mhash to compute a HMAC
// by Lance Rushing
$bytelen = 64; // byte length for md5
if (strlen($key) > $bytelen) {
$key = pack('H*', md5($key));
}
$key = str_pad($key, $bytelen, chr(0x00));
$ipad = str_pad('', $bytelen, chr(0x36));
$opad = str_pad('', $bytelen, chr(0x5c));
$k_ipad = $key ^ $ipad;
$k_opad = $key ^ $opad;
return md5($k_opad . pack('H*', md5($k_ipad . $data)));
}
/**
* Check connection state.
* @access public
* @return boolean True if connected.
*/
public function connected()
{
if (is_resource($this->smtp_conn)) {
$sock_status = stream_get_meta_data($this->smtp_conn);
if ($sock_status['eof']) {
// The socket is valid but we are not connected
$this->edebug(
'SMTP NOTICE: EOF caught while checking if connected',
self::DEBUG_CLIENT
);
$this->close();
return false;
}
return true; // everything looks good
}
return false;
}
/**
* Close the socket and clean up the state of the class.
* Don't use this function without first trying to use QUIT.
* @see quit()
* @access public
* @return void
*/
public function close()
{
$this->setError('');
$this->server_caps = null;
$this->helo_rply = null;
if (is_resource($this->smtp_conn)) {
// close the connection and cleanup
fclose($this->smtp_conn);
$this->smtp_conn = null; //Makes for cleaner serialization
$this->edebug('Connection: closed', self::DEBUG_CONNECTION);
}
}
/**
* Send an SMTP DATA command.
* Issues a data command and sends the msg_data to the server,
* finializing the mail transaction. $msg_data is the message
* that is to be send with the headers. Each header needs to be
* on a single line followed by a with the message headers
* and the message body being separated by and additional .
* Implements rfc 821: DATA
* @param string $msg_data Message data to send
* @access public
* @return boolean
*/
public function data($msg_data)
{
//This will use the standard timelimit
if (!$this->sendCommand('DATA', 'DATA', 354)) {
return false;
}
/* The server is ready to accept data!
* According to rfc821 we should not send more than 1000 characters on a single line (including the CRLF)
* so we will break the data up into lines by \r and/or \n then if needed we will break each of those into
* smaller lines to fit within the limit.
* We will also look for lines that start with a '.' and prepend an additional '.'.
* NOTE: this does not count towards line-length limit.
*/
// Normalize line breaks before exploding
$lines = explode("\n", str_replace(array("\r\n", "\r"), "\n", $msg_data));
/* To distinguish between a complete RFC822 message and a plain message body, we check if the first field
* of the first line (':' separated) does not contain a space then it _should_ be a header and we will
* process all lines before a blank line as headers.
*/
$field = substr($lines[0], 0, strpos($lines[0], ':'));
$in_headers = false;
if (!empty($field) && strpos($field, ' ') === false) {
$in_headers = true;
}
foreach ($lines as $line) {
$lines_out = array();
if ($in_headers and $line == '') {
$in_headers = false;
}
//Break this line up into several smaller lines if it's too long
//Micro-optimisation: isset($str[$len]) is faster than (strlen($str) > $len),
while (isset($line[self::MAX_LINE_LENGTH])) {
//Working backwards, try to find a space within the last MAX_LINE_LENGTH chars of the line to break on
//so as to avoid breaking in the middle of a word
$pos = strrpos(substr($line, 0, self::MAX_LINE_LENGTH), ' ');
//Deliberately matches both false and 0
if (!$pos) {
//No nice break found, add a hard break
$pos = self::MAX_LINE_LENGTH - 1;
$lines_out[] = substr($line, 0, $pos);
$line = substr($line, $pos);
} else {
//Break at the found point
$lines_out[] = substr($line, 0, $pos);
//Move along by the amount we dealt with
$line = substr($line, $pos + 1);
}
//If processing headers add a LWSP-char to the front of new line RFC822 section 3.1.1
if ($in_headers) {
$line = "\t" . $line;
}
}
$lines_out[] = $line;
//Send the lines to the server
foreach ($lines_out as $line_out) {
//RFC2821 section 4.5.2
if (!empty($line_out) and $line_out[0] == '.') {
$line_out = '.' . $line_out;
}
$this->client_send($line_out . self::CRLF);
}
}
//Message data has been sent, complete the command
//Increase timelimit for end of DATA command
$savetimelimit = $this->Timelimit;
$this->Timelimit = $this->Timelimit * 2;
$result = $this->sendCommand('DATA END', '.', 250);
$this->recordLastTransactionID();
//Restore timelimit
$this->Timelimit = $savetimelimit;
return $result;
}
/**
* Send an SMTP HELO or EHLO command.
* Used to identify the sending server to the receiving server.
* This makes sure that client and server are in a known state.
* Implements RFC 821: HELO
* and RFC 2821 EHLO.
* @param string $host The host name or IP to connect to
* @access public
* @return boolean
*/
public function hello($host = '')
{
//Try extended hello first (RFC 2821)
return (boolean)($this->sendHello('EHLO', $host) or $this->sendHello('HELO', $host));
}
/**
* Send an SMTP HELO or EHLO command.
* Low-level implementation used by hello()
* @see hello()
* @param string $hello The HELO string
* @param string $host The hostname to say we are
* @access protected
* @return boolean
*/
protected function sendHello($hello, $host)
{
$noerror = $this->sendCommand($hello, $hello . ' ' . $host, 250);
$this->helo_rply = $this->last_reply;
if ($noerror) {
$this->parseHelloFields($hello);
} else {
$this->server_caps = null;
}
return $noerror;
}
/**
* Parse a reply to HELO/EHLO command to discover server extensions.
* In case of HELO, the only parameter that can be discovered is a server name.
* @access protected
* @param string $type - 'HELO' or 'EHLO'
*/
protected function parseHelloFields($type)
{
$this->server_caps = array();
$lines = explode("\n", $this->helo_rply);
foreach ($lines as $n => $s) {
//First 4 chars contain response code followed by - or space
$s = trim(substr($s, 4));
if (empty($s)) {
continue;
}
$fields = explode(' ', $s);
if (!empty($fields)) {
if (!$n) {
$name = $type;
$fields = $fields[0];
} else {
$name = array_shift($fields);
switch ($name) {
case 'SIZE':
$fields = ($fields ? $fields[0] : 0);
break;
case 'AUTH':
if (!is_array($fields)) {
$fields = array();
}
break;
default:
$fields = true;
}
}
$this->server_caps[$name] = $fields;
}
}
}
/**
* Send an SMTP MAIL command.
* Starts a mail transaction from the email address specified in
* $from. Returns true if successful or false otherwise. If True
* the mail transaction is started and then one or more recipient
* commands may be called followed by a data command.
* Implements rfc 821: MAIL FROM:
* @param string $from Source address of this message
* @access public
* @return boolean
*/
public function mail($from)
{
$useVerp = ($this->do_verp ? ' XVERP' : '');
return $this->sendCommand(
'MAIL FROM',
'MAIL FROM:<' . $from . '>' . $useVerp,
250
);
}
/**
* Send an SMTP QUIT command.
* Closes the socket if there is no error or the $close_on_error argument is true.
* Implements from rfc 821: QUIT
* @param boolean $close_on_error Should the connection close if an error occurs?
* @access public
* @return boolean
*/
public function quit($close_on_error = true)
{
$noerror = $this->sendCommand('QUIT', 'QUIT', 221);
$err = $this->error; //Save any error
if ($noerror or $close_on_error) {
$this->close();
$this->error = $err; //Restore any error from the quit command
}
return $noerror;
}
/**
* Send an SMTP RCPT command.
* Sets the TO argument to $toaddr.
* Returns true if the recipient was accepted false if it was rejected.
* Implements from rfc 821: RCPT TO:
* @param string $address The address the message is being sent to
* @access public
* @return boolean
*/
public function recipient($address)
{
return $this->sendCommand(
'RCPT TO',
'RCPT TO:<' . $address . '>',
array(250, 251)
);
}
/**
* Send an SMTP RSET command.
* Abort any transaction that is currently in progress.
* Implements rfc 821: RSET
* @access public
* @return boolean True on success.
*/
public function reset()
{
return $this->sendCommand('RSET', 'RSET', 250);
}
/**
* Send a command to an SMTP server and check its return code.
* @param string $command The command name - not sent to the server
* @param string $commandstring The actual command to send
* @param integer|array $expect One or more expected integer success codes
* @access protected
* @return boolean True on success.
*/
protected function sendCommand($command, $commandstring, $expect)
{
if (!$this->connected()) {
$this->setError("Called $command without being connected");
return false;
}
//Reject line breaks in all commands
if (strpos($commandstring, "\n") !== false or strpos($commandstring, "\r") !== false) {
$this->setError("Command '$command' contained line breaks");
return false;
}
$this->client_send($commandstring . self::CRLF);
$this->last_reply = $this->get_lines();
// Fetch SMTP code and possible error code explanation
$matches = array();
if (preg_match("/^([0-9]{3})[ -](?:([0-9]\\.[0-9]\\.[0-9]) )?/", $this->last_reply, $matches)) {
$code = $matches[1];
$code_ex = (count($matches) > 2 ? $matches[2] : null);
// Cut off error code from each response line
$detail = preg_replace(
"/{$code}[ -]" .
($code_ex ? str_replace('.', '\\.', $code_ex) . ' ' : '') . "/m",
'',
$this->last_reply
);
} else {
// Fall back to simple parsing if regex fails
$code = substr($this->last_reply, 0, 3);
$code_ex = null;
$detail = substr($this->last_reply, 4);
}
$this->edebug('SERVER -> CLIENT: ' . $this->last_reply, self::DEBUG_SERVER);
if (!in_array($code, (array)$expect)) {
$this->setError(
"$command command failed",
$detail,
$code,
$code_ex
);
$this->edebug(
'SMTP ERROR: ' . $this->error['error'] . ': ' . $this->last_reply,
self::DEBUG_CLIENT
);
return false;
}
$this->setError('');
return true;
}
/**
* Send an SMTP SAML command.
* Starts a mail transaction from the email address specified in $from.
* Returns true if successful or false otherwise. If True
* the mail transaction is started and then one or more recipient
* commands may be called followed by a data command. This command
* will send the message to the users terminal if they are logged
* in and send them an email.
* Implements rfc 821: SAML FROM:
* @param string $from The address the message is from
* @access public
* @return boolean
*/
public function sendAndMail($from)
{
return $this->sendCommand('SAML', "SAML FROM:$from", 250);
}
/**
* Send an SMTP VRFY command.
* @param string $name The name to verify
* @access public
* @return boolean
*/
public function verify($name)
{
return $this->sendCommand('VRFY', "VRFY $name", array(250, 251));
}
/**
* Send an SMTP NOOP command.
* Used to keep keep-alives alive, doesn't actually do anything
* @access public
* @return boolean
*/
public function noop()
{
return $this->sendCommand('NOOP', 'NOOP', 250);
}
/**
* Send an SMTP TURN command.
* This is an optional command for SMTP that this class does not support.
* This method is here to make the RFC821 Definition complete for this class
* and _may_ be implemented in future
* Implements from rfc 821: TURN
* @access public
* @return boolean
*/
public function turn()
{
$this->setError('The SMTP TURN command is not implemented');
$this->edebug('SMTP NOTICE: ' . $this->error['error'], self::DEBUG_CLIENT);
return false;
}
/**
* Send raw data to the server.
* @param string $data The data to send
* @access public
* @return integer|boolean The number of bytes sent to the server or false on error
*/
public function client_send($data)
{
$this->edebug("CLIENT -> SERVER: $data", self::DEBUG_CLIENT);
set_error_handler(array($this, 'errorHandler'));
$result = fwrite($this->smtp_conn, $data);
restore_error_handler();
return $result;
}
/**
* Get the latest error.
* @access public
* @return array
*/
public function getError()
{
return $this->error;
}
/**
* Get SMTP extensions available on the server
* @access public
* @return array|null
*/
public function getServerExtList()
{
return $this->server_caps;
}
/**
* A multipurpose method
* The method works in three ways, dependent on argument value and current state
* 1. HELO/EHLO was not sent - returns null and set up $this->error
* 2. HELO was sent
* $name = 'HELO': returns server name
* $name = 'EHLO': returns boolean false
* $name = any string: returns null and set up $this->error
* 3. EHLO was sent
* $name = 'HELO'|'EHLO': returns server name
* $name = any string: if extension $name exists, returns boolean True
* or its options. Otherwise returns boolean False
* In other words, one can use this method to detect 3 conditions:
* - null returned: handshake was not or we don't know about ext (refer to $this->error)
* - false returned: the requested feature exactly not exists
* - positive value returned: the requested feature exists
* @param string $name Name of SMTP extension or 'HELO'|'EHLO'
* @return mixed
*/
public function getServerExt($name)
{
if (!$this->server_caps) {
$this->setError('No HELO/EHLO was sent');
return null;
}
// the tight logic knot ;)
if (!array_key_exists($name, $this->server_caps)) {
if ($name == 'HELO') {
return $this->server_caps['EHLO'];
}
if ($name == 'EHLO' || array_key_exists('EHLO', $this->server_caps)) {
return false;
}
$this->setError('HELO handshake was used. Client knows nothing about server extensions');
return null;
}
return $this->server_caps[$name];
}
/**
* Get the last reply from the server.
* @access public
* @return string
*/
public function getLastReply()
{
return $this->last_reply;
}
/**
* Read the SMTP server's response.
* Either before eof or socket timeout occurs on the operation.
* With SMTP we can tell if we have more lines to read if the
* 4th character is '-' symbol. If it is a space then we don't
* need to read anything else.
* @access protected
* @return string
*/
protected function get_lines()
{
// If the connection is bad, give up straight away
if (!is_resource($this->smtp_conn)) {
return '';
}
$data = '';
$endtime = 0;
stream_set_timeout($this->smtp_conn, $this->Timeout);
if ($this->Timelimit > 0) {
$endtime = time() + $this->Timelimit;
}
while (is_resource($this->smtp_conn) && !feof($this->smtp_conn)) {
$str = @fgets($this->smtp_conn, 515);
$this->edebug("SMTP -> get_lines(): \$data is \"$data\"", self::DEBUG_LOWLEVEL);
$this->edebug("SMTP -> get_lines(): \$str is \"$str\"", self::DEBUG_LOWLEVEL);
$data .= $str;
// If response is only 3 chars (not valid, but RFC5321 S4.2 says it must be handled),
// or 4th character is a space, we are done reading, break the loop,
// string array access is a micro-optimisation over strlen
if (!isset($str[3]) or (isset($str[3]) and $str[3] == ' ')) {
break;
}
// Timed-out? Log and break
$info = stream_get_meta_data($this->smtp_conn);
if ($info['timed_out']) {
$this->edebug(
'SMTP -> get_lines(): timed-out (' . $this->Timeout . ' sec)',
self::DEBUG_LOWLEVEL
);
break;
}
// Now check if reads took too long
if ($endtime and time() > $endtime) {
$this->edebug(
'SMTP -> get_lines(): timelimit reached (' .
$this->Timelimit . ' sec)',
self::DEBUG_LOWLEVEL
);
break;
}
}
return $data;
}
/**
* Enable or disable VERP address generation.
* @param boolean $enabled
*/
public function setVerp($enabled = false)
{
$this->do_verp = $enabled;
}
/**
* Get VERP address generation mode.
* @return boolean
*/
public function getVerp()
{
return $this->do_verp;
}
/**
* Set error messages and codes.
* @param string $message The error message
* @param string $detail Further detail on the error
* @param string $smtp_code An associated SMTP error code
* @param string $smtp_code_ex Extended SMTP code
*/
protected function setError($message, $detail = '', $smtp_code = '', $smtp_code_ex = '')
{
$this->error = array(
'error' => $message,
'detail' => $detail,
'smtp_code' => $smtp_code,
'smtp_code_ex' => $smtp_code_ex
);
}
/**
* Set debug output method.
* @param string|callable $method The name of the mechanism to use for debugging output, or a callable to handle it.
*/
public function setDebugOutput($method = 'echo')
{
$this->Debugoutput = $method;
}
/**
* Get debug output method.
* @return string
*/
public function getDebugOutput()
{
return $this->Debugoutput;
}
/**
* Set debug output level.
* @param integer $level
*/
public function setDebugLevel($level = 0)
{
$this->do_debug = $level;
}
/**
* Get debug output level.
* @return integer
*/
public function getDebugLevel()
{
return $this->do_debug;
}
/**
* Set SMTP timeout.
* @param integer $timeout
*/
public function setTimeout($timeout = 0)
{
$this->Timeout = $timeout;
}
/**
* Get SMTP timeout.
* @return integer
*/
public function getTimeout()
{
return $this->Timeout;
}
/**
* Reports an error number and string.
* @param integer $errno The error number returned by PHP.
* @param string $errmsg The error message returned by PHP.
* @param string $errfile The file the error occurred in
* @param integer $errline The line number the error occurred on
*/
protected function errorHandler($errno, $errmsg, $errfile = '', $errline = 0)
{
$notice = 'Connection failed.';
$this->setError(
$notice,
$errno,
$errmsg
);
$this->edebug(
$notice . ' Error #' . $errno . ': ' . $errmsg . " [$errfile line $errline]",
self::DEBUG_CONNECTION
);
}
/**
* Extract and return the ID of the last SMTP transaction based on
* a list of patterns provided in SMTP::$smtp_transaction_id_patterns.
* Relies on the host providing the ID in response to a DATA command.
* If no reply has been received yet, it will return null.
* If no pattern was matched, it will return false.
* @return bool|null|string
*/
protected function recordLastTransactionID()
{
$reply = $this->getLastReply();
if (empty($reply)) {
$this->last_smtp_transaction_id = null;
} else {
$this->last_smtp_transaction_id = false;
foreach ($this->smtp_transaction_id_patterns as $smtp_transaction_id_pattern) {
if (preg_match($smtp_transaction_id_pattern, $reply, $matches)) {
$this->last_smtp_transaction_id = $matches[1];
}
}
}
return $this->last_smtp_transaction_id;
}
/**
* Get the queue/transaction ID of the last SMTP transaction
* If no reply has been received yet, it will return null.
* If no pattern was matched, it will return false.
* @return bool|null|string
* @see recordLastTransactionID()
*/
public function getLastTransactionID()
{
return $this->last_smtp_transaction_id;
}
}
================================================
FILE: lib/phpmailer/extras/EasyPeasyICS.php
================================================
* @author Manuel Reinhard
*
* Built with inspiration from
* http://stackoverflow.com/questions/1463480/how-can-i-use-php-to-dynamically-publish-an-ical-file-to-be-read-by-google-calend/1464355#1464355
* History:
* 2010/12/17 - Manuel Reinhard - when it all started
* 2014 PHPMailer project becomes maintainer
*/
/**
* Class EasyPeasyICS.
* Simple ICS data generator
* @package phpmailer
* @subpackage easypeasyics
*/
class EasyPeasyICS
{
/**
* The name of the calendar
* @var string
*/
protected $calendarName;
/**
* The array of events to add to this calendar
* @var array
*/
protected $events = array();
/**
* Constructor
* @param string $calendarName
*/
public function __construct($calendarName = "")
{
$this->calendarName = $calendarName;
}
/**
* Add an event to this calendar.
* @param string $start The start date and time as a unix timestamp
* @param string $end The end date and time as a unix timestamp
* @param string $summary A summary or title for the event
* @param string $description A description of the event
* @param string $url A URL for the event
* @param string $uid A unique identifier for the event - generated automatically if not provided
* @return array An array of event details, including any generated UID
*/
public function addEvent($start, $end, $summary = '', $description = '', $url = '', $uid = '')
{
if (empty($uid)) {
$uid = md5(uniqid(mt_rand(), true)) . '@EasyPeasyICS';
}
$event = array(
'start' => gmdate('Ymd', $start) . 'T' . gmdate('His', $start) . 'Z',
'end' => gmdate('Ymd', $end) . 'T' . gmdate('His', $end) . 'Z',
'summary' => $summary,
'description' => $description,
'url' => $url,
'uid' => $uid
);
$this->events[] = $event;
return $event;
}
/**
* @return array Get the array of events.
*/
public function getEvents()
{
return $this->events;
}
/**
* Clear all events.
*/
public function clearEvents()
{
$this->events = array();
}
/**
* Get the name of the calendar.
* @return string
*/
public function getName()
{
return $this->calendarName;
}
/**
* Set the name of the calendar.
* @param $name
*/
public function setName($name)
{
$this->calendarName = $name;
}
/**
* Render and optionally output a vcal string.
* @param bool $output Whether to output the calendar data directly (the default).
* @return string The complete rendered vlal
*/
public function render($output = true)
{
//Add header
$ics = 'BEGIN:VCALENDAR
METHOD:PUBLISH
VERSION:2.0
X-WR-CALNAME:' . $this->calendarName . '
PRODID:-//hacksw/handcal//NONSGML v1.0//EN';
//Add events
foreach ($this->events as $event) {
$ics .= '
BEGIN:VEVENT
UID:' . $event['uid'] . '
DTSTAMP:' . gmdate('Ymd') . 'T' . gmdate('His') . 'Z
DTSTART:' . $event['start'] . '
DTEND:' . $event['end'] . '
SUMMARY:' . str_replace("\n", "\\n", $event['summary']) . '
DESCRIPTION:' . str_replace("\n", "\\n", $event['description']) . '
URL;VALUE=URI:' . $event['url'] . '
END:VEVENT';
}
//Add footer
$ics .= '
END:VCALENDAR';
if ($output) {
//Output
$filename = $this->calendarName;
//Filename needs quoting if it contains spaces
if (strpos($filename, ' ') !== false) {
$filename = '"'.$filename.'"';
}
header('Content-type: text/calendar; charset=utf-8');
header('Content-Disposition: inline; filename=' . $filename . '.ics');
echo $ics;
}
return $ics;
}
}
================================================
FILE: lib/phpmailer/extras/README.md
================================================
# PHPMailer Extras
These classes provide optional additional functions to PHPMailer.
These are not loaded by the PHPMailer autoloader, so in some cases you may need to `require` them yourself before using them.
## EasyPeasyICS
This class was originally written by Manuel Reinhard and provides a simple means of generating ICS/vCal files that are used in sending calendar events. PHPMailer does not use it directly, but you can use it to generate content appropriate for placing in the `Ical` property of PHPMailer. The PHPMailer project is now its official home as Manuel has given permission for that and is no longer maintaining it himself.
## htmlfilter
This class by Konstantin Riabitsev and Jim Jagielski implements HTML filtering to remove potentially malicious tags, such as `